【LeetCode热题100】双指针

class Solution {
public:
    void moveZeroes(vector<int>& nums) 
    {
        int dst = -1,cur = 0;
        while(cur<nums.size())
        {
            if(nums[cur] == 0)
            {
                cur++;
            }
            else
            {
                swap(nums[dst+1],nums[cur]);
                cur++;dst++;
            }
        }
    
    }
};

题目分析:对于数组分块/数组划分的问题,我们可以使用双指针算法进行求解(这道题的“指针”是数组下标)。我们使用两个“指针”dst和cur,将数组划分为三个区间:[0,dst]已处理过且不为0的区间、[dst+1,cur-1]已处理过且为0的区间、[cur,n-1]待处理的区间。两个指针的作用:cur--从左往右扫描数组,遍历数组;dst--已处理的区间内,非零元素的最后一个位置。cur向后依次遍历,可能会有两种情况:1.cur指向的元素为0,cur++  2.cur指向的元素不为0,swap(nums[dst+1],nums[cur]),cur++,dst++。由于刚开始时没有已处理的区间,所以dst=-1,cur=0。

 

class Solution {
public:
    void duplicateZeros(vector<int>& arr) 
    {
        //1.找到要复写的最后一个数
        int cur = 0,dst = -1;
        while(cur < arr.size())
        {
            if(arr[cur] == 0) dst+=2;
            else dst++;
            if(dst >= arr.size()-1)
            {
                break;
            }
            cur++;
        }
        //处理一下边界情况:dst加了两下,加过了
        if(dst == arr.size())
        {
            arr[dst-1]=0 ;
            dst-=2;
            cur--;
        }
        //2.从后向前复写
        while(cur >= 0)
        {
            if(arr[cur] == 0)
            {
                arr[dst]=arr[dst-1]=0;
                dst -= 2;
                cur--;
            }
            else
            {
                arr[dst]=arr[cur];
                dst--;
                cur--;
            }
        }
    }
};

 题目分析:这道题是对数组元素进行操作,可以考虑双指针算法。整体思路就是先根据“异地”操作,然后优化成双指针下的“就地”操作。我们首先想到的是另外创建一个数组,进行异地操作,一个指针cur指向原数组,另一个指针dst指向新建数组,cur依次遍历每一个数,按照要求在新建数组进行复写。然而,这样的做法明显不符合“就地操作”的要求,因此,我们需要让cur和dst都指向原数组,如果它们都指向了起始位置,这样遇到复写0之后就会覆盖后面的元素,肯定会出问题。所以我们需要从后往前复写,dst需要指向数组最后一个元素,cur要指向最后一个“复写”的数,因此,我们总共有两步:1.先找到最后一个“复写”的数  2.“从后向前”完成复写操作。经过多次实践,可以通过如下方法找到最后一个复写的数:1)先判断cur位置的值 2)决定dest向后移动一步或者两步 3)判断一下dest是否已经结束 4)cur++  在开始之前,我们需要先让dst指向-1,cur指向0(因为最终dst指向最后一个要复写的位置,刚开始的时候并不知道最后一个位置在哪,因此设为-1),当这个循环退出时,cur指向了最后一个“复写”的数。此外,在找最后一个要复写的数的过程中,我们还可能因为cur最后一次指向0,dst需要加两次,跳到了arr的外面,即下标为n的位置,对于这种情况,我们需要单独处理一下,让arr[n-1]=0,然后dst回退两次,cur--即可。

class Solution {
public:
    int bitSum(int n)
    {
        int sum = 0;
        while(n)
        {
            int lastbit = n%10;
            sum += lastbit*lastbit;
            n /= 10;
        }
        return sum;
    }

    bool isHappy(int n) 
    { 
        int slow = n, fast = bitSum(n);
        while(slow != fast)
        {
            slow = bitSum(slow);
            fast = bitSum(bitSum(fast));
        }

        return slow == 1;
    }
};

题目分析:给定一个正整数,按照题目给定的过程重复计算,会有两种结果:其一是这个数在某一次变成1,那么之后会一直循环1;第二种是这个数会在某个圈里无限循环,但是不是1。因此,一定会进入循环(鸽巢原理),我们可以通过判断当进入循环后,这个值是不是1来确定这个数是不是快乐数。

那么如何找到进入循环的某一个数呢?这个就需要用到快慢指针的思想:

(一)定义快慢指针

(二)慢指针每次向后移动一步,快指针每次向后移动两步

(三)判断相遇时候的值即可

class Solution {
public:
    int maxArea(vector<int>& height) 
    { 
        int max= 0;
        int i =0,j=height.size()-1;
        int v = 0;//体积
        while( i < j)
        {
            v = (j - i) * (height[i]<height[j]?height[i]:height[j]);
            if(v > max) max = v;
            if(height[i] < height[j]) 
                i++;
            else 
                j--;
        }

        return max;

    }
};

题目分析:第一种方法就是暴力求解,遍历每一个可能得组合,计算出它们的体积的最大值,但是这种方法是运行超时的,因此我们还要另辟蹊径。

第二种方法比较巧妙,我们先来看一个简单例子,如果数组是[6,2,5,4],那我们先计算最外面两个数的组合:6和4,宽度是3,高度是max(6,4)=4,体积=3*4=12,我们再往里面深入,如果是4和2组合,首先它们的宽度变成2(往里深入宽度必定减少),高度变成2(高度必定是≤4,),很显然,宽度必定减少,高度最多保持不变,那么4和往里面的树组合体积必定减少,因此就不用考虑4和里面任何数的组合,所以直接忽略4(两侧--6和4中较小的那一个值),数组变成[6,2,5],然后继续依照上面逻辑,先计算出最外侧两个数的体积,和上一个体积比较,最后直到整个数组被遍历完!

class Solution {
public:
    int triangleNumber(vector<int>& nums) 
    {
        //1.优化
        sort(nums.begin(),nums.end());
        int size = nums.size();
        int fixi = size-1;
        int left = 0;
        int right = fixi-1;
        int ret = 0;
        while(fixi > 1)
        {
            while(left < right)
            {
                if(nums[left] + nums[right] > nums[fixi])
                {
                    ret += right - left;
                    right--;
                }
                else
                {
                  left++;
                }
            }
            fixi--;
            left = 0;
            right = fixi-1;
        }

        return ret;
    }
        
};

题目分析:如果要判断的三个数是有序的(a  <  b  <  c),那么只需要判断一次即可,就是判断 a+b是否大于c,因为b+c肯定大于a,a+c也肯定大于b。

这道题有两种解法,一种是暴力解法,遍历这个数组得到三个数,依次判断是否符合,这种解法的时间复杂度很高。另外一种解法,也是利用双指针的思想,比如给出的数组是[2,3,5,5,7,8,9,12],先固定一个最大的c,也就是最后的12,然后让left指向剩下的最小的,即2,再让right指向剩下最大的,即9,判断left+right和固定最大值c=12的大小关系,那么就有两种情况:

        1.left+right>c,那么right和left与right中间的任意一个组合都符合条件,不用继续遍历中间的,符合的组数为right-left,再让right--;

        2.left+right<=c,那么,left和left与right中间任意一个组合都不符合条件,left++;

当left=right后,这一回合遍历结束,再让固定的最大值c向前调整一个,然后继续上述步骤即可。

class Solution {
public:
    vector<int> twoSum(vector<int>& price, int target)
    {
        vector<int> ret;
        int size = price.size();
        int left = 0;
        int right = size - 1;
        while(left < right)
        {
            if(price[left] + price[right] < target)
            {
                left++;
            }
            else if(price[left] + price[right] > target)
            {
                right--;
            }
            else
            {
                return {price[left],price[right]};//隐式类型转换
            }
        }
        //虽然我们知道这条语句不会被执行,但是为了照顾编译器,
        //需要在每条返回路径上都返回
        return {1,1};

    }
};

题目分析:和有效三角形的个数的思想非常类似,也是用到双指针的思想。刚开始的时候,左指针left指向数组最左,右指针right指向数组最右,整个数组是单调递增的,先判断left+right和target的关系,分为三种情况:

        1.left+right > target,那么right和left与right之间的任意数之和都大于target,就没有必要继续遍历了,让right--;

        2.left+right < target,那么left和left与right之间的任意数之和都小于target,也没有必要继续遍历了,让left++;

        3.left+right > target,那么直接返回即可,走隐式类型转换。

需要注意的是,需要在循环外加一个return语句,这样编译器才不会报错,认为每条路径都会有返回值。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) 
    {
        sort(nums.begin(),nums.end());
        int size = nums.size();
        vector<vector<int>> vv;

        for(int i = 0; i < size - 2; )
        {
            if(nums[i] > 0) break;
            int left = i + 1;
            int right = size - 1;
            while(left < right)
            {
                if(-1 * nums[i] < nums[left] + nums[right])
                {
                    right--;
                }
                else if(-1 * nums[i] > nums[left] + nums[right])
                {
                    left++;
                }
                else
                {
                    vv.push_back({nums[i],nums[left],nums[right]});
                    left++;
                    right--; 
                    //去重操作 left right
                    while(left < right && nums[left] == nums[left-1]) left++;
                    while(left < right && nums[right] == nums[right+1]) right--;
                }
            }
            i++;
            //去重i
            while(i < size && nums[i] == nums[i-1])
            {
                i++;
            }          
        }
        return vv;
    }
};

题目分析:这道题依然是有两种解法:暴力解法和优化解法,

暴力解法:先对数组进行排序,然后对数组中的每三个数进行遍历,当找到三个数的符合条件后,将这三个数的组合插入set中,这样方便去重,但是暴力解法的时间复杂度是O(N3)。

优化解法:先对数组进行排序,然后固定一个数a(从最左侧开始固定),在该数后面的区间内,利用上题“双指针”算法,快速找到这个区间内和为-a的两个数。

处理细节

1.去重:找到一种结果之后,left和right指针要跳过重复元素,直到找到下一个不重复的元素,当使用完一次双指针算法后,a也要跳过重复元素。

2.不漏:找到一种结果之后,不要“停”,缩小区间,继续寻找

3.小优化:只有a≤0时,才有可能在后面的区间找到两个数之和=-a,因此a只需要遍历非正值即可。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) 
    {
        //1.排序
        sort(nums.begin(),nums.end());
        //2.按“双指针”查找
        vector<vector<int>> ret;
        int n = nums.size();
        for(int i = 0;i<n;)//固定a
        {
            for(int j=i+1;j<n;)//固定b
            {
                long long val = (long long)target - nums[i] - nums[j];
                int left = j+1;
                int right = n-1;
                while(left < right)
                {
                    int sum = nums[left] + nums[right];
                    if(val > sum)
                    {
                        left++;
                    }
                    else if(val < sum)
                    {
                        right--;
                    }
                    else
                    {
                        ret.push_back({nums[i],nums[j],nums[left],nums[right]});
                        left++;
                        right--;
                        //去重1
                        while(left < right && nums[left] == nums[left-1]) left++;
                        while(left < right && nums[right] == nums[right+1]) right--;
                    }          
                }
                //去重2
                j++;
                while(j < n && nums[j-1] == nums[j]) j++;
            }
            //去重3
            i++;
            while(i < n && nums[i-1] == nums[i]) i++;
        }

        return ret;
    }
};

题目分析:这道题会借用上面三数之和的思想,首先还是对数组进行排序,然后先固定最左边第一个数a,然后从后面区间找出和为target-a的三个数,然后就是借助三数之和的思想,先固定剩下区间的第一个数b,再从剩下区间找和为target-a-b的两个数,然后继续遍历,要和三数之和一样要做到不重不漏。

1. Two Sum 2. Add Two Numbers 3. Longest Substring Without Repeating Characters 4. Median of Two Sorted Arrays 5. Longest Palindromic Substring 6. ZigZag Conversion 7. Reverse Integer 8. String to Integer (atoi) 9. Palindrome Number 10. Regular Expression Matching 11. Container With Most Water 12. Integer to Roman 13. Roman to Integer 14. Longest Common Prefix 15. 3Sum 16. 3Sum Closest 17. Letter Combinations of a Phone Number 18. 4Sum 19. Remove Nth Node From End of List 20. Valid Parentheses 21. Merge Two Sorted Lists 22. Generate Parentheses 23. Swap Nodes in Pairs 24. Reverse Nodes in k-Group 25. Remove Duplicates from Sorted Array 26. Remove Element 27. Implement strStr() 28. Divide Two Integers 29. Substring with Concatenation of All Words 30. Next Permutation 31. Longest Valid Parentheses 32. Search in Rotated Sorted Array 33. Search for a Range 34. Find First and Last Position of Element in Sorted Array 35. Valid Sudoku 36. Sudoku Solver 37. Count and Say 38. Combination Sum 39. Combination Sum II 40. First Missing Positive 41. Trapping Rain Water 42. Jump Game 43. Merge Intervals 44. Insert Interval 45. Unique Paths 46. Minimum Path Sum 47. Climbing Stairs 48. Permutations 49. Permutations II 50. Rotate Image 51. Group Anagrams 52. Pow(x, n) 53. Maximum Subarray 54. Spiral Matrix 55. Jump Game II 56. Merge k Sorted Lists 57. Insertion Sort List 58. Sort List 59. Largest Rectangle in Histogram 60. Valid Number 61. Word Search 62. Minimum Window Substring 63. Unique Binary Search Trees 64. Unique Binary Search Trees II 65. Interleaving String 66. Maximum Product Subarray 67. Binary Tree Inorder Traversal 68. Binary Tree Preorder Traversal 69. Binary Tree Postorder Traversal 70. Flatten Binary Tree to Linked List 71. Construct Binary Tree from Preorder and Inorder Traversal 72. Construct Binary Tree from Inorder and Postorder Traversal 73. Binary Tree Level Order Traversal 74. Binary Tree Zigzag Level Order Traversal 75. Convert Sorted Array to Binary Search Tree 76. Convert Sorted List to Binary Search Tree 77. Recover Binary Search Tree 78. Sum Root to Leaf Numbers 79. Path Sum 80. Path Sum II 81. Binary Tree Maximum Path Sum 82. Populating Next Right Pointers in Each Node 83. Populating Next Right Pointers in Each Node II 84. Reverse Linked List 85. Reverse Linked List II 86. Partition List 87. Rotate List 88. Remove Duplicates from Sorted List 89. Remove Duplicates from Sorted List II 90. Intersection of Two Linked Lists 91. Linked List Cycle 92. Linked List Cycle II 93. Reorder List 94. Binary Tree Upside Down 95. Binary Tree Right Side View 96. Palindrome Linked List 97. Convert Binary Search Tree to Sorted Doubly Linked List 98. Lowest Common Ancestor of a Binary Tree 99. Lowest Common Ancestor of a Binary Search Tree 100. Binary Tree Level Order Traversal II
评论 45
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值