快慢指针法是一种十分常用的方法,其对于判断链表等是否具有无限循环的情况十分简单方便。
例如:现具有一个1,2,3,4,5,1,2,3,4,5…的无限循环的链表情况(链表的尾5指向链表的头1的情况),可以定义一个快指针,一个慢指针,初始两个指针都指向第一个1,让慢指针每次向后移一个节点,让快指针每次后移两个节点,在不断循环中最终两个指针都会指向相同节点位置的1,即如果存在循环,最终快慢指针会相遇,但若没有无限循环的情况,则快指针会提前到达终止条件,例如:现有一个链表:1,2,3,4,5 定义快慢指针各一个,慢指针一次后移一个节点,快指针一次后移两个节点,最终快指针会提前指向NULL到达终止条件,快慢指针不会相遇。
例题一:
思考:(快慢指针法),先将链表节点只有一个或没有节点的特殊情况放在代码开头返回false,然后定义struct ListNode*类型的t1和t2两个指针,初始化都为head,然后建立while循环,每次让t1后移一个节点,让t2后移两个节点,如果是环形链表t1和t2最终会相遇,如果不是环状链表,t2会先指向NULL出while循环,若出while循环就返回false,如果在while循环中t1 == t2,就返回true
代码:
bool hasCycle(struct ListNode *head) {
if (head == NULL || head -> next == NULL) {
return false;
}
struct ListNode *t1 = head, *t2 = head;
while (t2 != NULL && t1 != NULL) {
t1 = t1 -> next;
if (t2 -> next != NULL) {
t2 = t2 -> next -> next;
} else {
t2 = NULL;
}
if (t2 == t1) {
return true;
}
}
return false;
}
思考:(快慢指针法)slow(慢指针)每往后算一次,fast(快指针)就往后算两次,如果是快乐数的话,fast会先到1,slow会后到1,return的值就会为真,如果不是快乐数,fast会在不断的循环中与slow相遇(即值会相等)且值都不会为1,return的值就会为假
代码:
int jack(int n) {
int t1, sum = 0;
while (n) {
t1 = n % 10;
sum += t1 * t1;
n /= 10;
}
return sum;
}
bool isHappy(int n){
int slow = jack(n), fast = jack(jack(n));
while (slow != fast) {
slow = jack(slow);
fast = jack(jack(fast));
}
return slow == 1;
}
双指针法一般常用于对与数组的处理,大体思路是定义一个左指针left,再定义一个右指针right,让两个指针分别从数组的两端按所需要求循环处理,最终实现所需的结果。
简单例子:要逆序一个数组或字符(例如数组和字符为a[]),只需要在满足条件left < right的条件下,交换a[left]和a[right],每次交换完后left加一、right减一,出循环时就可实现所需结果。
例题一:
思考:
(双指针法)定义一个快指针fast一个慢指针slow,当slow <= fast时执行循环,如果nums[fast]的值等于val那就fast减一,如果nums[slow]的值不等于val那就slow加一,当nums[fast] != val 且 nums[slow] == val时将nums[fast]的值赋给nums[slow],且让slow加一,fast减一,出循环后返回新数组的长度slow即可。
代码:
int removeElement(int* nums, int numsSize, int val){
if (nums == NULL || numsSize == 0)
return 0;
int slow = 0,fast = numsSize - 1;
while (slow <= fast) {
if (nums[fast] == val) {
--fast;
continue;
}
if (nums[slow] != val) {
++slow;
continue;
}
nums[slow] = nums[fast];
++slow;
--fast;
}
return slow;
}
例题二:
思考:(双指针法)left指向最左边,right指向最右边,i初始化为0,建立for循环(循环进行的条件是i <= right),用i遍历一遍数组,i只遇到0或2时才交换,i遇到2时和right交换,然后让right减一,z在while循环中将所有的2都放在最后面,出while循环后判断交换后的nums[i]是否等于0,等于0时与left交换(left只回为1,因为0已经被++left放在不会被操作的最前头了),然后让left加一,i遇见1时不做处理,当出for循环时,0都在1的左边,2都在1的右边,返回数组名nums即可。
代码:
void sortColors(int* nums, int numsSize){
int left = 0, right = numsSize - 1, i, t;
for (i = 0; i <= right; ++i) {
while (i <= right && nums[i] ==2) {
t = nums[right];
nums[right] = nums[i];
nums[i] = t;
--right;
}
if (nums[i] == 0) {
t = nums[left];
nums[left] = nums[i];
nums[i] = t;
++left;
}
}
return nums;
}
滑动窗口法常用于处理数组等相关问题,可以用时间复杂度o(n)和空间复杂度o(1)去实现一些操作,是简化数组操作的算法中的一种。
例题:
思考:滑动窗口法)先将特殊情况放在开头返回,定义两个指针start和end,初始都为0,分别表示子数组的开头和结尾,定义sum初始化为0,用来存储从start到end之间的子数组元素的和,每一轮迭代将nums[end]加到sum,如果sum >= s,则更新子数组的最小长度(此时子数组的最小长度是end - start + 1),然后将nums[start]从sum中减去并将start右移,直到sum < s,在此过程中同样更新子数组的最小长度。在每一轮迭代的最后,将end右移
代码:
int minSubArrayLen(int target, int* nums, int numsSize){
if (numsSize == 0) {
return 0;
}
int cnt = numsSize + 1, start = 0, end = 0, sum = 0;
while (end < numsSize) {
sum += nums[end];
while (sum >= target) {
cnt = fmin(cnt, end - start + 1);
sum -= nums[start];
++start;
}
++end;
}
return cnt == numsSize + 1 ? 0 : cnt;
}