字符串反转
方法一:递归
原地反转字符串是否代表了空间复杂度为常数?
不,原地反转字符串是一种不使用辅助数据结构的算法。
我们使用递归的方法去反转字符串,它是原地反转,但是空间复杂度却不是常数级空间,因为递归过程中使用了堆栈空间。
算法过程
- 我们实现递归函数 helper,它接受两个参数:left 左指针和 right 右指针。
- 如果 left>=right,不做任何操作。
- 否则交换 s[left] 和 s[right] 和调用 helper(left + 1, right - 1)。
- 首次调用函数我们传递首尾指针反转整个字符串 return helper(0, len(s) - 1)。
1
2
3
4
5
6
7
8
9
10
11
12
public void helper(char[] s, int left, int right) {
if (left >= right) return;
char tmp = s[left];
s[left++] = s[right];
s[right--] = tmp;
helper(s, left, right);
}
public void reverseString(char[] s) {
helper(s, 0, s.length - 1);
}
复杂度分析
时间复杂度:\mathcal{O}(N)O(N)。执行了 N/2N/2 次的交换。
空间复杂度:\mathcal{O}(N)O(N),递归过程中使用的堆栈空间。
方法二:双指针法
双指针法是使用两个指针,一个左指针 left,右指针 right,开始工作时 left 指向首元素,right 指向尾元素。交换两个指针指向的元素,并向中间移动,直到两个指针相遇。
算法过程
- 将 left 指向首元素,right 指向尾元素。
- 当 left<right:
- 交换 s[left] 和 s[right]。
- left++
- right++
1 | // 双指针 |
测试用例
1 | let str = "123456abcdef" |
复杂度分析
时间复杂度:\mathcal{O}(N)O(N)。执行了 N/2N/2 次的交换。
空间复杂度:\mathcal{O}(1)O(1),只使用了常数级空间。
反转链表
双指针法
- 定义两个指针pre cur : pre在前 cur 在后
- 如果cur节点不为空,设置pre.next = cur 实现一次翻转
- 翻转后pre 、cur同步向前一步
- 当pre为空终止循环
1 | func reverseList(_ head: ListNode?) -> ListNode? { |
1 | 递归法核心:reversList(head) = reverseList(head.next) |
反转链表 II
题目描述:反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。说明:1 ≤ m ≤ n ≤ 链表长度
第一步:找到待反转节点的前一个节点。
第二步:反转m到n这部分。
第三步:将反转的起点的next指向反转的后面一部分。
第四步:将第一步找到的节点指向反转以后的头节点。
1 | public ListNode reverseBetween(ListNode head, int m, int n) { |