LeetCode高频题刷题笔记(七)双指针

基础知识

双指针法基本都是应用在数组,字符串与链表的题目上


题目

1.寻找重复数( LeetCode 287

难度: 中等
题目表述:
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
代码(C++):

class Solution {
public:
	// 二分查找
    int findDuplicate(vector<int>& nums) {
        int l = 1, r = nums.size() - 1, ans;
        while (l <= r) {
            int mid = (l + r) >> 1;
            int cnt = 0;
            for (int i = 0; i < nums.size(); i++) {
                cnt += nums[i] <= mid;
            }
            if (cnt <= mid) {
                l = mid + 1;
            } else {
                r = mid - 1;
                ans = mid;
            }
        }
        return ans;
    }
	// 二进制
    int findDuplicate(vector<int>& nums) {
        int n = nums.size(), ans = 0;
        int bit_max = 17; // nums最大值是1e5,17是可覆盖的最小位数 = 131072
        while (!(n - 1) >> bit_max) {
            bit_max--;
        }
        for (int i = 0; i < bit_max; i++) {
            int x = 0, y = 0;
            for (int j = 0; j < n; j++) {
                if (nums[j] & 1 << i)
                    x++;
                if (j >= 1 && (j & 1 << i))
                    y++; 
            }
            if (x > y) {
                ans |= 1 << i;
            }
        }
        return ans;
    }
    // 快慢指针
    int findDuplicate(vector<int>& nums) {
        int slow = 0, fast = 0;
        do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while(slow != fast);
        slow = 0;
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }
};

题解: 二分查找 / 二进制 / 快慢指针
二分查找:数组中的数在1-n之间,小于中间数的数出现的次数满足单调性,不重复的情况下,出现次数必然小于等于中间数,重复数必然出现在中间数的右侧,反之则表明中间数左侧必有重复数,因此可用二分法。
二进制:考虑到第 i 位,我们记 nums 数组中二进制展开后第 i 位为 1 的数有 x 个,数字 [1,n] 这 n 个数二进制展开后第 i 位为 1 的数有 y 个,那么当且仅当 x>y,重复的数第 i 位为 1 。按位与&、按位或|。
快慢指针(「Floyd 判圈算法」(又称龟兔赛跑算法),它是一个检测链表是否有环的算法):我们对 nums 数组建图,每个位置 i 连一条 i →nums[i] 的边。由于存在的重复的数字 target,因此 target 这个位置一定有起码两条指向它的边,因此整张图一定存在环,且我们要找到的 target 就是这个环的入口,那么整个问题就等价于 二(9)环形链表 II。如 1 4 6 6 6 2 3:1->4->6->3->6出现环,环入口就是重复值。


2.验证回文串( LeetCode 125

难度: 简单
题目表述:
如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。字母和数字都属于字母数字字符。给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。
代码(C++):

class Solution {
public:
    bool isPalindrome(string s) {
        string a = "";
        for (auto &c: s) {
            if (c >= 'A' &&  c <= 'Z')
                a += c - 'A' + 'a';
            else if ((c >= 'a' &&  c <= 'z') || (c >= '0' && c <= '9'))
                a += c;    
        }
        int l = 0,r = a.size() - 1;
        while (l < r) {
            if (a[l] != a[r])  
                return false;
            l++;
            r--;
        } 
        return true;
    }
    bool isPalindrome(string s) {
        int l = 0,r = s.size() - 1;
        while (l < r) {
            while (l < r && !isalnum(s[l])) {
                l++;
            }
            while (l < r && !isalnum(s[r])) {
                r--;
            }
            if (l < r) {
                if (tolower(s[l]) != tolower(s[r]) ) {
                    return false;
                }
                l++;
                r--;
            }
        } 
        return true;
    }
};

题解:
isalnum判字母或数字字符、tolower转小写、toupper转大写、a(a.rbegin(), a.rend())反转字符串


3.x 的平方根 ( LeetCode 69

难度: 简单
题目表述:
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
代码(C++):

class Solution {
public:
	// 二分查找
    int mySqrt(int x) {
        int l = 0, r = x / 2 + 1;
        while (l < r) {
            long mid = (l + r + 1) >> 1;
            if (mid * mid > x) {
                r = mid - 1;
            } else {
                l = mid;
            }
        }
        return l;
    }
    // 牛顿迭代
    int mySqrt(int x) {
        if (x == 0) return 0;
        double C = x, x0 = x;
        while (true) {
            double x1 = (x0 + C / x0) / 2; 
            if (x1 - x0 < 1e-6) {
                break;
            }
            x0 = x1;
        }
        return int(x0);
    }
};

题解: 二分查找 / 牛顿迭代
牛顿迭代法是一种可以用来快速求解函数零点的方法。


4.移除元素 ( LeetCode 27

难度: 简单
题目表述:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
代码(C++):

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int l = 0, r = nums.size() - 1;
        while (l <= r) {
            if (nums[l] == val) {
                nums[l] = nums[r];
                r--;
            } else {
                l++;
            }
        }
        return l;
    }
};

题解:


5.反转字符串 ( LeetCode 344

难度: 简单
题目表述:
将输入的字符串反转过来。
代码(C++):

class Solution {
public:
    void reverseString(vector<char>& s) {
        int l = 0, r = s.size() - 1;
        while (l < r) {
            swap(s[l], s[r]);
            l++;
            r--;
        }
    }
};

题解:


6.翻转字符串里的单词 ( LeetCode 151

难度: 中等
题目表述:
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
代码(C++):

class Solution {
public:
    string reverseWords(string s) {
        string res = "";
        int n = s.size();
        int l = n - 1;
        while (l >= 0) {
            while (l >= 0 && s[l] == ' ') {
                l--;
            }
            if (l < 0) break;
            int r = l;
            while (l >= 0 && s[l] != ' ') {
                l--;
            }
            res += s.substr(l + 1, r - l);
            res += ' ';
        }
        return res.substr(0, res.size() - 1);
    }
};

题解:


7.最接近的三数之和( LeetCode 16

难度: 中等
题目表述:
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。返回这三个数的和。
代码(C++):

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        sort(nums.begin(), nums.end());
        int n = nums.size(), res = nums[0] + nums[1] + nums[2];
        for (int i = 0; i < n - 2; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            if (nums[i] + nums[n - 2] + nums[n - 1] < target) {
                int newSum = nums[i] + nums[n - 2] + nums[n - 1];
                res = (abs(newSum - target) < abs(res - target)) ? newSum : res;
                continue;
            }
            if (nums[i] + nums[i + 1] + nums[i + 2] > target) {
                int newSum = nums[i] + nums[i + 1] + nums[i + 2];
                res = (abs(newSum - target) < abs(res - target)) ? newSum : res;
                continue;
            }
            int l = i + 1, r = n - 1;
            while (l < r) {
                int newSum = nums[i] + nums[l] + nums[r];
                if (newSum == target) return target;
                res = (abs(newSum - target) < abs(res - target)) ? newSum : res;
                if (newSum > target) {
                    int r0 = r - 1;
                    // 移动到下一个不相等的元素
                    while (l < r0 && nums[r0] == nums[r]) r0--;
                    r = r0;
                } else {
                    int l0 = l + 1;
                    // 移动到下一个不相等的元素
                    while (l0 < r && nums[l0] == nums[l]) l0++;
                    l = l0;
                }
            }
        }
        return res;
    }
};

题解:
和三数之和、四数之和类似,使用双指针代替两重循环来枚举所有的可能情况


8.三数之和( LeetCode 15

难度: 中等
题目表述:
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
代码(C++):

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int n = nums.size();
        vector<vector<int>> res;
        for (int i = 0; i < n - 2; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break;
            if (nums[i] + nums[n - 2] + nums[n - 1] < 0) continue;
            int l = i + 1, r = n - 1;
            while (l < r) {
                if (nums[i] + nums[l] + nums[r] == 0) {
                    res.push_back({nums[i], nums[l], nums[r]});
                    while (l < r && nums[l] == nums[l + 1]) {
                        l++;
                    }
                    l++;
                    while (l < r && nums[r] == nums[r - 1]) {
                        r--;
                    }
                    r--;
                } else if (nums[i] + nums[l] + nums[r] < 0) {
                    l++;
                } else {
                    r--;
                }
            }
        }
        return res;
    }
};

题解:


9.四数之和( LeetCode 18

难度: 中等
题目表述:
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出不复用、和为target且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复)。
代码(C++):

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        int n = nums.size();
        sort(nums.begin(), nums.end());
        for (int i = 0; i < n - 3; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;
            if ((long) nums[i] + nums[n - 3] + nums[n - 2] + nums[n - 1] < target) continue;
            for (int j = i + 1; j < n - 2; j++) {
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) break;
                if ((long) nums[i] + nums[j] + nums[n - 2] + nums[n - 1] < target) continue;
                int l = j + 1, r = n - 1;
                while (l < r) {
                    long sum = (long) nums[i] + nums[j] + nums[l] + nums[r];
                    if (sum == target) {
                        res.push_back({nums[i], nums[j], nums[l], nums[r]});
                        while (l < r && nums[l] == nums[l + 1]) {
                            l++;
                        }
                        l++;
                        while (l < r && nums[r] == nums[r - 1]) {
                            r--;
                        }
                        r--;
                    } else if (sum < target) {
                        l++;
                    } else {
                        r--;
                    }
                }
            }
        }
        return res;
    }
};

题解: 排序 + 双指针
两重循环分别枚举前两个数,使用双指针枚举剩下的两个数,根据排序后递增的特性,可以加入剪枝。


10.两数之和( LeetCode 1

难度: 简单
题目表述:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
代码(C++):

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> mp;
        for (int i = 0; i < nums.size(); i++) {
            if (mp.find(target - nums[i]) != mp.end()) return {mp[target - nums[i]], i};
            mp[nums[i]] = i;
        }
        return {};
    }
};

题解: 哈希


小结

具有单调性应联想到二分查找法,双指针可抵消掉一重循环。
「Floyd 判圈算法」(又称龟兔赛跑算法),检测链表是否有环的算法,并且返回入环点:
(1)先快慢指针找相遇点;
(2)相遇之后,让slow回到起点,再和fast一起一步一步走,直至相遇,相遇点就是入环点。


x数之和模板:

  1. 升序排序
  2. 枚举第一个
    2.1 判当前和前一个相同 continue;
    2.2 判(当前 + 当前的后几个) > target break;(看题意决定break还是continue)
    2.3 判 (当前 + 最后几个) < target continue; (看题意决定break还是continue)
  3. (类似第一个)枚举第二个
  4. (类似第一个) 枚举第…个
  5. 双指针枚举最后两个数,可以加入while判重以避免将重复的结果多次返回。
// 1.升序排序
sort();
// 2.枚举第一个
for (int i = 0; i < n - (x-1); i++) {
	//	1.1 判重以保证和上一次枚举的元素不同
	if (i > 0 && nums[i] == nums[i - 1]) continue;
	// 1.2 判第一个元素和后面紧跟着的x-1个元素之和是否>target,如果是,就说明后面的枚举和一定都是大于target,直接break;
	if ((long) nums[i] + nums[i + 1] + nums[i + ...]  > target) break;
	// 1.3 判第一个元素和最后的x-1个元素之和是否<target,如果是,就说明该轮枚举和的最大值都是小于target的,就没必要再继续枚举后面几个元素了,直接进入下一轮continue;
	if ((long) nums[i] + nums[n - (x-1)] + nums[n - ...] < target) continue;
	// 3.枚举第二个
	for (int j = i + 1; j < n - (x-2); j++) {
		// 2.1 判重
		// 2.2 判前几个
		// 2.3 判后几个
		// 4.双指针枚举最后两个数
		while (l < r) {
            long sum = (long) nums[i] + nums[j] + nums[l] + nums[r];
            if (sum == target) {
                return / push_back
                // 判重,避免将重复的结果多次返回
                while (l < r && nums[l] == nums[l + 1]) {
                    l++;
                }
                l++;
                // 判重,避免将重复的结果多次返回
                while (l < r && nums[r] == nums[r - 1]) {
                    r--;
                }
                r--;
            } else if (sum < target) {
                l++;
            } else {
                r--;
            }
        }
	}
}

参考链接

玩转 LeetCode 高频 100 题
LeetCode 刷题攻略

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值