(1)左右指针
一个指针指向开端,另一指针指向末尾
关键在于左右指针如何移动来满足题目规则。
例子:
11. 盛最多水的容器
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
示例 3:
输入:height = [4,3,2,1,4]
输出:16
示例 4:
输入:height = [1,2,1]
输出:2
提示:
n == height.length
2 <= n <= 105
0 <= height[i] <= 104
双指针(i,j),i为height左指针,j为height右指针 因所求square=两个指针指向的数字中较小值∗指针之间的距离
决定的。如果我们移动数字较大的那个指针,那么前者「两个指针指向的数字中较小值」不会增加,后者「指针之间的距离」会减小,那么这个乘积会减小。因此,我们移动数字较大的那个指针是不合理的。因此,我们移动
数字较小的那个指针。(参考快排)
int maxArea(int* height, int heightSize){
int i=0,j=heightSize-1;
int max=0;
while(i<j){
int square=fmin(height[i],height[j])*(j-i);
if(height[i]<height[j])i++;
else j--;
if(max<square) max=square;
}
return max;
}
977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
提示:
1 <= nums.length <= 104
-104 <= nums[i] <= 104
nums 已按 非递减顺序 排序
因为平方按照非递减排序,指针移动nums[i]*nums[i]与nums[j]*nums[j]中大的那一个
//两指针i,j,一个指向数组开头一个指向数组末端
class Solution {
public int[] sortedSquares(int[] nums) {
int []array=new int[nums.length];
int i=0,j=nums.length-1;
int k=nums.length-1;
while(i<=j){
int a=nums[i]*nums[i];
int b=nums[j]*nums[j];
if(a>b)
{
i++;
array[k--]=a;
}
else {
j--;
array[k--]=b;
}
}
return array;
}
}
15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:
输入:nums = []
输出:[]
示例 3:
输入:nums = [0]
输出:[]
//排序+双指针
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
if(nums.size()==0)return res;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size()-1;i++){
int num=-1*nums[i];
if(i>0&&nums[i]==nums[i-1]) continue;//满足不重复组合则第一个数与for循环上一轮的第一个数不能相等
//左指针a,右指针b
int a=i+1,b=nums.size()-1;//a=i+1开始若是从0开始会导致重复
while(a<b){
if(a>i+1&&nums[a]==nums[a-1]){//同样消除重复,第二个数与while循环上一轮的第二个数不能相等
a++;
continue;
}
if(nums[a]+nums[b]<num)a++;
else if(nums[a]+nums[b]>num)b--;
else{
vector<int>temp;
temp.push_back(nums[i]);
temp.push_back(nums[a]);
temp.push_back(nums[b]);
res.push_back(temp);
a++;b--;
}
}
}
return res;
}
};
对数组排序,这里是三个数求和为0,先固定一个数i,for循环遍历,再将另两个数求和视为所求和为-nums[i]的组合分别记为左指针a和右指针b。
意思为这三个数中其中一个数为nums[i]的时候另外两个数之和nums[a]+nums[b]=-nums[i]的所有组合
(2)快慢指针
面试题 02.02. 返回倒数第 k 个节点
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。
示例:
输入: 1->2->3->4->5 和 k = 2
输出: 4
说明:
给定的 k 保证是有效的。
这题要求链表的倒数第k个节点,最简单的方式就是使用两个指针,第一个指针先移动k步,然后第二个指针再从头开始,这个时候这两个指针同时移动,当第一个指针到链表的末尾的时候,返回第二个指针即可。
作者:sdwwld
来源:力扣(LeetCode)
int kthToLast(struct ListNode* head, int k){
struct ListNode*first,*second;
first=head;
second=head;
while(k--)first=first->next;
while(first){
first=first->next;
second=second->next;
}
return second->val;
}
面试题 02.08. 环路检测
给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。若环不存在,请返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
我们使用两个指针,fast 与 slow。它们起始都位于链表的头部。随后,slow 指针每次向后移动一个位置,而 fast
指针向后移动两个位置。如果链表中存在环,则fast 指针最终将再次与 slow 指针在环中相遇。如下图所示,设链表中环外部分的长度为 aa。slow 指针进入环后,又走了 bb 的距离与fast 相遇。此时,fast
指针已经走完了环的 nn 圈,因此它走过的总距离为
a+n(b+c)+b=a+(n+1)b+n。
根据题意,任意时刻,fast 指针走过的距离都为slow 指针的 2倍。因此,我们有a+(n+1)b+nc=2(a+b)⟹a=c+(n−1)(b+c)
有了 a=c+(n-1)(b+c)a=c+(n−1)(b+c) 的等量关系,我们会发现:从相遇点到入环点的距离加上 n-1
圈的环长,恰好等于从链表头部到入环点的距离。因此,当发现 slow 与fast 相遇时,我们再额外使用一个指针 ptr。起始,它指向链表头部;随后,它和slow
每次向后移动一个位置。最终,它们会在入环点相遇。
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode *slow = head, *fast = head;
while (fast != NULL) {
slow = slow->next;
if (fast->next == NULL) {
return NULL;
}
fast = fast->next->next;
if (fast == slow) {
struct ListNode* ptr = head;
while (ptr != slow) {
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}
return NULL;
}
面试题 02.06. 回文链表
编写一个函数,检查输入的链表是否是回文的。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
避免使用 O(n)额外空间的方法就是改变输入。
我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。虽然不需要恢复也能通过测试用例,但是使用该函数的人通常不希望链表结构被更改。
该方法虽然可以将空间复杂度降到
O(1),但是在并发环境下,该方法也有缺点。在并发环境下,函数运行时需要锁定其他线程或进程对链表的访问,因为在函数执行过程中链表会被修改。算法
整个流程可以分为以下五个步骤:
找到前半部分链表的尾节点。 反转后半部分链表。 判断是否回文。 恢复链表。 返回结果。
执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。若链表有奇数个节点,则中间的节点应该看作是前半部分。步骤二可以使用「206. 反转链表」问题中的解决方法来反转链表的后半部分。
步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。
步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* prev = NULL;
struct ListNode* curr = head;
while (curr != NULL) {
struct ListNode* nextTemp = curr->next;
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
struct ListNode* endOfFirstHalf(struct ListNode* head) {
struct ListNode* fast = head;
struct ListNode* slow = head;
while (fast->next != NULL && fast->next->next != NULL) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
bool isPalindrome(struct ListNode* head) {
if (head == NULL) {
return true;
}
// 找到前半部分链表的尾节点并反转后半部分链表
struct ListNode* firstHalfEnd = endOfFirstHalf(head);
struct ListNode* secondHalfStart = reverseList(firstHalfEnd->next);
// 判断是否回文
struct ListNode* p1 = head;
struct ListNode* p2 = secondHalfStart;
bool result = true;
while (result && p2 != NULL) {
if (p1->val != p2->val) {
result = false;
}
p1 = p1->next;
p2 = p2->next;
}
// 还原链表并返回结果
firstHalfEnd->next = reverseList(secondHalfStart);
return result;
}