【LeetCode 笔记】双指针

双指针套路

双指针分为同向遍历和反向遍历。

1. 同向

同向
通用步骤:

  1. int i, j (初始化,通常令二者为0)
  2. while j < arr.length:
    如果需要保留arr[j],则使用arr[i]来保存,同时i后移为下次遍历作准备,即arr[i++] = arr[j++];
    否则跳过,继续遍历。

2. 反向

反向
通用步骤:

  1. int i = 0, j = arr.length - 1;
  2. while i <= j:
    具体操作取决于题目要求
    至少在该方向上移动一个指针

15. 三数之和

题目描述

思路

  • 对数组进行排序
  • 找一个基准 nums[i],使用L和R指针遍历nums[i++1 … numsSize - 1],初始L=i+1, R=numsSize-1
    • 当三数之和sum < 0时,L++;sum > 0 时,R–。但是若基准元素已经大于零,则无需遍历
    • 当sum == 0时,将答案加入返回数组中

当遇到多个相同的值时有重复答案,这就需要去重:

  • 基准元素nums[i] == nums[i - 1]时,结束本次循环
  • 当L < R && nums[L] == nums[L + 1] 即左指针的元素等于下一个元素时,L++。右指针同理
/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
int comp(const void *a, const void *b) { return *(int *)a - *(int *)b; }

int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    *returnSize = 0;
    if(numsSize < 3){
        return NULL;
    }
    int n = numsSize;
    qsort(nums, n, sizeof(int), comp);	//快速排序

    int **res = malloc(sizeof(int *) * n * n);
    *returnColumnSizes = malloc(sizeof(int) * n * n);

    for(int i = 0; i < n; i++){
        if(nums[i] > 0)
            break;
        if(i > 0 && nums[i] == nums[i - 1])
            continue;
        int L = i + 1, R = n - 1;
        while(L < R){
            int sum = nums[i] + nums[L] + nums[R];
            if(sum == 0){
                res[*returnSize] = malloc(sizeof(int) * 3);
                res[*returnSize][0] = nums[i];
                res[*returnSize][1] = nums[L];
                res[*returnSize][2] = nums[R];
                (*returnColumnSizes)[*returnSize] = 3;	// 记录列数
                (*returnSize)++;
                while(L < R && nums[L] == nums[L + 1])  L++;
                while(L < R && nums[R] == nums[R - 1])  R--;
                L++;
                R--;
            } else if(sum < 0)  L++;
            else if(sum > 0) R--;
        }
    }
    return res;
}

88. 合并两个有序数组

题目描述

思路

  • 利用 length 记录 nums1 复制操作后的总长度,ij 指针分别遍历两个数组
  • 当数组1的元素小于数组2的元素,即 nums1[i] <= nums2[j] && i < length 时,i 指针向后移动,直到找到第一个大于 nums2[j] 的元素
    • 将nums1[i]以后的元素向后移动一个单位,最后将nums2[j]的值赋给nums1[i](类似于插入排序)。
    • 然后令j++nums1 的长度 length++
  • 重复此操作,直到 length == m + n
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
    int length = m;
	int i = 0, j = 0;
	while(length <= m + n && j < n) {
		while(nums1[i] <= nums2[j] && i < length)
			i++;						//找到第一个大于nums2[j]的元素
		for(int k = length - 1; k >= i; k--)
			nums1[k + 1] = nums1[k];	//依次向后移动
		nums1[i] = nums2[j];			//复制到nums1
		j++;
		length++;
	}
}

125. 验证回文串

题目描述

代码实现

/*算法思想:1.设置双指针i,j分别指向字符串头和字符串尾
           2.当表头与表尾指针i,j未相遇时,指针i向右遍历,直到遇到第一个字母或数字字符,指针j向左遍历,直至遇到第一个字母或数字字符
           3.判断遍历到元素是否相等,若相等则指针继续相向前进
*/
char Tolower (char a)   //将字符转为小写,若本身已经是小写或是数字则直接返回
{
    if(a >= 'A' && a <= 'Z')
        return a+32;
    else
        return a;
}
bool isNumOrChar (char a)   //判断是否为字符或者数字
{
    if(a >= '0' && a <= '9')
        return true;
    else if(a >= 'a' && a <= 'z')
        return true;
    else if(a >= 'A' && a <= 'Z')
        return true;
    else
        return false;
}
bool isPalindrome(char * s){
    int len = strlen(s);
    if(len <= 1)
        return true;
    int i = 0, j = len - 1;
    while(i < j)
    {
        while(i < j && !(isNumOrChar(s[i])))    i++;    //指针i向右遍历,直到遇到第一个字母或数字字符
        while(i < j && !(isNumOrChar(s[j])))    j--;    //指针j向左遍历,直至遇到第一个字母或数字字符
            if(i < j && Tolower(s[i]) != Tolower(s[j])) 
                return false;
        i++;
        j--;
    }
    return true;
}

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 0 0 移动到数组的末尾,同时保持非零元素的相对顺序。

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

1. 必须在原数组上操作,不能拷贝额外的数组。
2. 尽量减少操作次数。

解题思路

方法:双指针
用两个指针i, j从头到尾依次遍历:

  • 遇到零元素就记录该位置
  • 遇到非零元素就进行替换零的操作

当其中一个指针i进行完一次遍历,此时所有非零元素已经全部移动到相对前面的位置,只需要将剩余位置上的元素置为零即可。

例如:

代码实现

void moveZeroes(int* nums, int numsSize){
    int i, j = 0;
    for(i = 0; i < numsSize; i++){
        if(nums[i] != 0){   //遇到非零元素进行替换零
            nums[j++] = nums[i];
        }
        // else 继续查找非零元素
    }

    // 将剩余位置上的元素置为零
    while(j < numsSize){
        nums[j++] = 0;
    }
}

345. 反转字符串中的元音字母

bool isVowel(char c){
    if(c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' 
    || c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U')
        return true;
    return false;
}

char * reverseVowels(char * s){
    int i = 0, j = strlen(s) - 1;
    while(i < j){
        if(isVowel(s[i]) && isVowel(s[j])){
            char temp = s[i];
            s[i] = s[j];
            s[j] = temp;
            i++;
            j--;
        } else if(isVowel(s[i]) && !isVowel(s[j])){
            j--;
        } else if(!isVowel(s[i]) && isVowel(s[j])){
            i++;
        } else {
            i++;
            j--;
        }
    }
    return s;
}

26. 删除排序数组中的重复项

题目描述

思路

数组完成排序后,我们可以放置两个指针 i i i j j j,其中 i i i 是慢指针,而 j j j 是快指针。只要 n u m s [ i ] = n u m s [ j ] nums[i] = nums[j] nums[i]=nums[j],我们就增加 j j j 以跳过重复项。

当我们遇到 n u m s [ j ] ≠ n u m s [ i ] nums[j] \neq nums[i] nums[j]=nums[i] 时,跳过重复项的运行已经结束,因此我们必须把它( n u m s [ j ] nums[j] nums[j])的值复制到 n u m s [ i + 1 ] nums[i+1] nums[i+1]。然后递增 i i i,接着我们将再次重复相同的过程,直到 j j j 到达数组的末尾为止。

int removeDuplicates(int* nums, int numsSize){
    if (numsSize == 0) return 0;
    int i = 0;
    for (int j = 1; j < numsSize; j++) {
        if (nums[j] != nums[i]) {
            i++;
            nums[i] = nums[j];
        }
    }
    return i + 1;
}

19. 删除链表的倒数第N个节点

给定一个链表,删除链表的倒数第 n n n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:

给定的 n n n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

思路

快慢指针,快指针先走n步,然后快慢一起走,直到快指针走到最后,要注意的是可能是要删除第一个节点,这个时候可以直接返回head -> next

代码

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* removeNthFromEnd(struct ListNode* head, int n){
    if(!head || !head -> next) return NULL;
    struct ListNode * fast = head, *slow = head;
    for(int i = 0; i < n; i++){
        fast = fast -> next;
    }
    if(!fast){
        return head -> next;    
    }
    
    while(fast -> next){
        fast = fast -> next;
        slow = slow -> next;
    }
    slow -> next = slow -> next -> next;
    return head;
}

面试题22. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

示例:

给定一个链表: 1->2->3->4->5, 和 k = 2.

返回链表 4->5.

思路 + 代码

与上面思路相同,使用快慢指针同步移动

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* getKthFromEnd(struct ListNode* head, int k){
    if(!head)
        return NULL;
    struct ListNode *fast = head, *slow = head;
    for(int i = 0; i < k && fast != NULL; i++){
        fast = fast->next;
    }
    while(fast){
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

面试题06. 从尾到头打印链表

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]

思路

  1. 先得到链表head的长度len --> 用来动态申请数组returnArray
  2. 遍历链表,把其中的元素赋值给数组returnArray
  3. 数组内部进行逆置
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int* reversePrint(struct ListNode* head, int* returnSize){
    int len = 0, i=0;
    struct ListNode *p = head;
    while(p){
        len++;
        p=p->next;
    }
    
    p = head;
    int *returnArray = (int *)malloc(len * sizeof(int));

    while(p){
        returnArray[i++] = p->val;
        p = p->next;
    }
    for(i=0;i<len/2;i++){
        int temp = returnArray[i];
        returnArray[i] = returnArray[len - i - 1];
        returnArray[len - i - 1] = temp;
    }
    (*returnSize) = len;

    return returnArray;
}

面试题 02.08. 环路检测

解题思路

经典的快慢指针问题

  1. 设置快慢指针
  2. 找到第一次相遇【快慢指针相遇】
  3. 再出发慢指针【快指针与从头结点出发的慢指针】
  4. 相遇即所求

代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *slow = head, *fast = head;
    // 快慢指针
    while (fast != NULL && fast->next != NULL){
        slow = slow->next;
        fast = fast->next->next;
        // 如果相遇了就break
        if (slow == fast) break;
    }
    
    // fast到了链表尾部,说明链表无环
    if (fast == NULL || fast->next == NULL)
    	return NULL;

	/*
	//上方if也可以这么写
	if(fast != slow) // 这个判逆必须加,因为上面循环跳出也可能是循环体结束了导致的
       	return NULL;
	*/
    
    // 慢指针从头开始, 快慢指针再一次相遇就是在环的起点
    slow = head;
    while (slow != fast){
        slow = slow->next;
        fast = fast->next;
    }
    return fast;
}

977. 有序数组的平方

给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。
输入:[-4,-1,0,3,10]
输出:[0,1,9,16,100]

解题思路

因为数组 A 已经排好序了, 所以可以说数组中的负数已经按照平方值降序排好了,数组中的非负数已经按照平方值升序排好了。

举一个例子,若给定数组为 [-3, -2, -1, 4, 5, 6],数组中负数部分 [-3, -2, -1] 的平方为 [9, 4, 1],数组中非负部分 [4, 5, 6] 的平方为 [16, 25, 36]。我们的策略就是从前向后遍历数组中的非负数部分,并且反向遍历数组中的负数部分

算法:

我们可以使用两个指针分别读取数组的非负部分与负数部分 —— 指针 k 反向读取负数部分,指针 j 正向读取非负数部分。

那么,现在我们就在使用两个指针分别读取两个递增的数组了(按元素的平方排序)。接下来,我们可以使用双指针的技巧合并这两个数组。

int* sortedSquares(int* A, int ASize, int* returnSize){
    short i, j = 0, k = ASize-1;
    int *res = malloc(ASize * sizeof(int));
    for(i = 0; i < ASize; i++){
        A[i] = A[i] * A[i];
    }
    while(i--){
        if(A[j] > A[k]){
            res[i] = A[j++];	//j正向读取非负数
        } else {
            res[i] = A[k--];	//k反向读取负数
        }
    }
    *returnSize=ASize;
    return res;
}

时间复杂度: O ( N ) O(N) O(N),其中 N N N 是数组 A 的长度。
空间复杂度: O ( N ) O(N) O(N)


5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

思路:从中间开始向两边扩散来判断回文串。考虑奇偶

for 0 <= i < len(s):
	找到以 s[i] 为中心的最长回文子串
	找到以 s[i] 和 s[i + 1] 为中心的最长回文子串
	更新答案

完整代码:

//截取字符串
char *subStr(char *s, int start, int len){
    char *dst = malloc(sizeof(char) * (len + 1));
    for(int i = 0; i < len; i++){
        dst[i] = s[start + i];
    }
    dst[len] = '\0';
    return dst; 
}

//寻找最长回文串
char *palindrome(char *s, int l, int r){
	//防止越界
    while(l >= 0 && r < strlen(s) && s[l] == s[r]){
    	//向两边展开
        l--; r++;
    }
    // 返回以 s[l] 和 s[r] 为中心的最长回文串
    return subStr(s, l + 1, r - l - 1);
}

char * longestPalindrome(char * s){
    char *res = "";
    for(int i = 0; i < strlen(s); i++){
        char *s1 = palindrome(s, i, i);		// 以 s[i] 为中心的最长回文子串(奇数)
        char *s2 = palindrome(s, i, i + 1);	// 以 s[i] 和 s[i + 1] 为中心的最长回文子串(偶数)
        res = strlen(res) > strlen(s1) ? res : s1;
        res = strlen(res) > strlen(s2) ? res : s2;
    }
    return res;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Beta Lemon

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值