九章算法第七课,两根指针&TwoSum问题

这篇博客介绍了多个数组算法问题的解决方案,如两数之和、奇偶分割数组、颜色分类等,强调了指针在解决问题中的应用,以及如何处理数组的边界条件和移动条件。还提及了数据结构的选择,如哈希表在处理重复元素时的重要性。

539. 移动零

class Solution {
public:
    void moveZeroes(vector<int> &nums) {
       if (nums.size()==0) { return; }
       //找到第一个是0的位置;
       int i;
       for (i=0;i<nums.size();i++) {
           if (nums[i]==0) break;
       }
       
       int pos=i; //pos记录下一个不是0的数要换到的位置
       //不管pos上是不是0,只要后面有非0就往前换
       for (i;i<nums.size();i++) {
           if (nums[i]!=0) {
               swap(nums[i],nums[pos]);
               pos++;
           }
       }
    }
};

415. 有效回文串
注意两个内置函数的用法:
transform(字符串头,字符串尾,新字符串头,::tolower||::toupper)
isalnum(char)

class Solution {
public:
    bool isPalindrome(string &s) {
        if (s.length()==0 || s.length()==1) return true;
        transform(s.begin(),s.end(),s.begin(),::tolower);
        
        int i=0; int j=s.length()-1;
        while (i<j) {
            while (!valid(s[i])) i++; //可以用::isalnum(s[i])替代
            while (!valid(s[j])) j--;
            if (i<j && s[i]!=s[j]) return false;
            i++;
            j--;
        }
    }
    bool valid(char c){
        if (c>='0' && c<='9' || c>='a' && c<='z')
            return true;
        else return false;
    }
};

31. 数组划分
注意最后i和j的位置需要判断一下;

class Solution {
public:
    int partitionArray(vector<int> &nums, int k) {
        if (nums.size()==0) return 0;
        
        int i=0;
        int j=nums.size()-1;
        while (i<j) {
            while (i<j && nums[i]<k) i++;
            while (i<j && nums[j]>=k) j--;
            if (i<j) {
                swap(nums[i],nums[j]);
                i++;
                j--;
            }
        }
        if (i==j) return nums[i]<k?i+1:i;
        else return j+1;
    }
};

?461. 无序数组K小元素
思想类似于快排,大体分好左右两部分,确定第k个数在左边还是在右边。由于只需要排一边,时间复杂度O(n)。
注意分割完了之后挑选两部分怎么选。一定要把i,j所在的数包含进去还不能出现划分完了和之前规模一样的情况。
所以需要循环条件设置为(i<=j),移动条件设置为i>pivot和j<pivot;使得ij重合出现在边界的情况不可能出现。

class Solution {
public:
    int kthSmallest(int k, vector<int> &nums) {
        return helper(nums,0,nums.size()-1,k);
    }
    int helper(vector<int>&nums,int start,int end,int k) {
        if (start==end) return nums[start];
        int pivot=nums[(start+end)/2];
        
        int i=start;
        int j=end;
        //要有等号,让i和j交叠或错开,不然可能会出现永远分不开的情况。
        while (i<=j) {
            while (i<=j && nums[i]<pivot) i++;
            while (i<=j && nums[j]>pivot) j--;
            if (i<=j) {
                swap(nums[i],nums[j]);
                i++;
                j--;
            }
        }
        //i,j的位置有两种可能,一种是错开,一种是重合,分情况讨论。
        //不管是错开还是重合,都要保证右边的边界进右边部分,左边的部分进左边部分。
        //如果左右都不是,说明j和i错开了,中间那个就是要找的值。
        if (k<=j-start+1) return helper(nums,start,j,k);
        if (k>=i-start+1) return helper(nums,i,end,k-(i-start));
        return nums[j+1];
    }
};

373. 奇偶分割数组
也是利用快排思想,但是不涉及到二次划分数组,所以不需要考虑会不会分不开的问题。

class Solution {
public:
    void partitionArray(vector<int> &nums) {
        if (nums.size()==0 || nums.size()==1) return;
        
        int i=0;
        int j=nums.size()-1;
        while (i<j) {
            while (i<j && nums[i]%2==1) i++;
            while (i<j && nums[j]%2==0) j--;
            if (i<j) {
                swap(nums[i],nums[j]);
                i++;
                j--;
            }
        }
    }
};

144. 交错正负数
第一步,将数组分为前正后负两部分;第二步,将其穿插;
注意正负数的个数不一定一样,需要分情况讨论。

class Solution {
public:
    void rerange(vector<int> &A) {
        if (A.size()==0 || A.size()==1) return;
        
        int i=0;
        int j=A.size()-1;
        while (i<j) {
            while (i<j && A[i]<0) i++;
            while (i<j && A[j]>0) j--;
            if (i<j) {
                swap(A[i],A[j]);
                i++;
                j--;
            }
        }
        //可以证明i一定是正数
            //负数多
            if (i>A.size()-i) interleave(A,1,A.size()-1);
            //正数多
            else if (i<A.size()-i) interleave(A,0,A.size()-2);
            //一样多
            else interleave(A,1,A.size()-2);
    }
    void interleave(vector<int> &A,int start,int end) {
        while (start<end) {
            swap(A[start],A[end]);
            start=start+2;
            end=end-2;
        }
    }
};


49. 字符大小写排序

class Solution {
public:
    void sortLetters(string &letters) {
        int i = 0, j = letters.size() - 1;
        while (i<j) {
            while (i<j && isLower(letters[i])) i++;
            while (i<j && !isLower(letters[j])) j--;
            if (i<j) {
                swap(letters[i],letters[j]);
                i++;
                j--;
            }
        }
    }
    bool isLower(char c) {
        return c >= 'a' && c <= 'z';
    }
};

148. 颜色分类
最简单的方法是for两边遍,第一遍记录个数,第二遍赋值。如果只能for一遍则需要使用指针的算法,注意边界条件和指针移动条件。

class Solution {
public:
    void sortColors(vector<int> &nums) {
        if (nums.size()==0 || nums.size()==1) return;
        
        int i=0;//i的左边都是0
        int j=nums.size()-1;//j的右边都是2
        int k=0;//找1
        while (k<=j) {//1的下标挪到2的起始位置时结束,要有等号,因为j可能还没处理
            if (nums[k]==0) {
                swap(nums[i],nums[k]);
                i++;
                //注意这里直接k++,不担心换过来的数字需要再换,因为i在k之前必定已经处理完了
                k++;
            }
            else if (nums[k]==1) k++;
            else if (nums[k]==2) {
                swap(nums[k],nums[j]);
                j--;
                //这里不可以k++,因为不知道换过来的是个啥
            }
        }
    }
};

143. 排颜色 II

class Solution {
public:
    void sortColors2(vector<int> &colors, int k) {
        if (colors.size()==0 ||colors.size()==1) return;

        sort(colors,0,colors.size()-1,1,k);
    }
    void sort(vector<int> &colors,int start,int end, 
              int colorsFrom, int colorsTo) {
        if (start>=end || colorsTo==colorsFrom) return;
        
        
        int mid=(colorsTo+colorsFrom)/2;
        int i=start;
        int j=end;
        //必须缩小范围,ij需要错开,所以加上=
        while (i<=j) {
            //由于mid是取上整,为了避免i左边没有数,应该把等于mid的情况归类到左边;
            while (i<=j && colors[i]<=mid) i++;
            while (i<=j && colors[j]>mid) j--;
            if (i<=j) {
                swap(colors[i],colors[j]);
                i++;
                j--;
            }
        }
        //排完之后j在左边,i在右边,需要缩小范围
        sort(colors,start,j,colorsFrom,mid);
        sort(colors,i,end,mid+1,colorsTo);
    }
};

烙饼排序
烙饼排序最简单的想法类似汉诺塔,先排好最下面的,然后排倒数第二个...最少操作两次就可以排好一个位置(假设要排第n个饼,饼位于x,则先将x以上的饼全部反转,x反转到最上面,然后再将n个饼反转)。加上最后两个饼只需要一次就能排好位置,因此最多2n-3次即可排好所有烙饼。
如果想减少反转次数找到最优解,只能通过搜索+剪枝的方法,动归等被证明不可以。
搜索即搜索所有可能的操作,记录达到有序一共需要的次数,但是时间复杂度会变的很高,因此需要剪枝。
剪枝即寻找提前出口的条件:
1.操作已超过2n-3次还没排好序;
2.已经排好序;
3.已经操作的次数+将要操作的次数大于2n-3次。由于每次操作只能使两个使一个烙饼与大小和它相邻的烙饼排到一起,因此想要有序最少的操作次数计算如下:

lowerBound = 0;
for(index = 0; index < size - 1; index ++) {
    if(cakeArray[index] - cakeArray[index+1] !=1 
    && cakeArray[index] - cakeArray[index+1] != -1)
        lowerBound++;
}

4.上次刚反转完,同一个饼,则不操作。

if(index == lastIndex) continue;

5.缓存从初始状态将到当前状态过程中的所以状态,然后搜索当前状态是否在缓存中出现过,如果出现过则退出。将之前的状态变成hashkey存储,在本状态退出之后需要删除此状态的hashkey。 


睡眠排序、面条排序、猴子排序,其中猴子排序用了随机洗牌算法,以及生成随机数

607. 两数之和 III-数据结构设计
注意考虑重复数字的问题,使用unordered_multimap。map、set都是使用红黑树实现的,而unordered_map、unordered_set才是哈希表实现的,multi表明可以有重复。

class TwoSum {
public:
    unordered_multiset<int> hash;
    void add(int number) {
        hash.insert(number);
    }
    bool find(int value) {
        for (auto n:hash) {
            if (value==2*n) {
                if (hash.count(n)>=2) return true;
            }
            else if (hash.count(value-n)>=1) return true;
        }
        return false;
    }
};

608. 两数和 II-输入已排序的数组

class Solution {
public:
    vector<int> twoSum(vector<int> &nums, int target) {
        vector<int> res;
        int i=0; int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]==target) {
                res.push_back(i+1);
                res.push_back(j+1);
                return res;
            }
            if (nums[i]+nums[j]<target) i++;
            else j--;
        }
    }
};

587. 两数之和 - 不同组成
不可以先去重!

class Solution {
public:
    int twoSum6(vector<int> &nums, int target) {
        sort(nums.begin(),nums.end());
        int res=0;
        int i=0; int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]==target) {
                res++;
                while (i<j && nums[i+1]==nums[i]) i++;
                while (i<j && nums[j-1]==nums[j]) j--;
            }
            if (nums[i]+nums[j]<target) i++;
            else j--;
        }
        return res;
    }
};

57. 三数之和
确定一个数,另外两个数用双指针。注意也要跳过重复数字的情况。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int> &numbers) {
        vector<vector<int>> res;
        sort(numbers.begin(),numbers.end());
        
        for (int i=0;i<=numbers.size()-3;i++) {
            if (i!=0 && numbers[i]==numbers[i-1]) continue;
            int target=-numbers[i];
            int left=i+1; int right=numbers.size()-1;
            while (left<right) {
                if (numbers[left]+numbers[right]==target) {
                    vector<int> tmp;
                    tmp.push_back(numbers[i]);
                    tmp.push_back(numbers[left]);
                    tmp.push_back(numbers[right]);
                    res.push_back(tmp);
                    while (left<right && numbers[left+1]==numbers[left]) left++;
                    while (left<right && numbers[right-1]==numbers[right]) right--;
                    left++; right--;
                }
                else if (numbers[left]+numbers[right]<target) left++;
                else right--;
            }
        }
        return res;
    }
};

609. 两数和-小于或等于目标值
算法也是不回头的,因为假如nums[i]+nums[j+1]>target && nums[i]+nums[j]<=target, 则nums[i+1]+nums[j+1]不可能小于等于target,j指针不必回头。

class Solution {
public:
    int twoSum5(vector<int> &nums, int target) {
        if (nums.size()<2) return 0; 
        sort(nums.begin(),nums.end());
        int res=0;
        
        int i=0;
        int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]<=target) {
                res=res+(j-i);
                i++;
            }
            if (nums[i]+nums[j]>target) j--;
        }
        return res;
    }
};

443. 两数之和 II
指针反过来移动即可。

class Solution {
public:
    int twoSum2(vector<int> &nums, int target) {
        if (nums.size()<2) return 0; 
        sort(nums.begin(),nums.end());
        int res=0;
        
        int i=0;
        int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]>target) {
                res=res+(j-i);
                j--;
            }
            if (nums[i]+nums[j]<=target) i++;
        }
        return res;
    }
};

533. 两数和的最接近值
因为i和j都是贴着最可能接近target的位置移动的,所以ij也都不回头。

class Solution {
public:
    int twoSumClosest(vector<int> &nums, int target) {
        sort(nums.begin(),nums.end());
        int res=INT_MAX;
        int i=0; int j=nums.size()-1;
        while (i<j) {
            if (nums[i]+nums[j]==target) {
                return 0;
            }
            res=min(res,abs(nums[i]+nums[j]-target));
            if (nums[i]+nums[j]<target) i++;
            else j--;
        }
        return res;
    }
};

59. 最接近的三数之和

class Solution {
public:
    int threeSumClosest(vector<int> &numbers, int target) {
        if (numbers.size()<3) return -1;
        
        sort(numbers.begin(),numbers.end());
        int res=INT_MAX;
        
        for (int i=0;i<numbers.size()-2;i++) {
            int j=i+1;
            int k=numbers.size()-1;
            while (j<k) {
                int sum=numbers[i]+numbers[j]+numbers[k];
                if (sum==target) return target;
                if (abs(sum-target)<abs(res-target)) res=sum;
                if (sum<target) j++;
                else k--;
            }
        }
        return res;
    }
};

58. 四数之和
和之前的方法一样即可。下面的代码尝试了一种使用哈希表的做法。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int> &numbers, int target) {
        sort(numbers.begin(),numbers.end());
        vector<vector<int>> res;
        if (numbers.size()<4) return res;
        unordered_map<int,vector<pair<int,int>>> twosum;
        
        //记录两数和
        for (int i=0;i<numbers.size()-1;i++)
            for (int j=i+1;j<numbers.size();j++) {
                if (twosum.find(numbers[i]+numbers[j])==twosum.end()) {
                    vector<pair<int,int>> tmp;
                    tmp.push_back(make_pair(i,j));
                    twosum[numbers[i]+numbers[j]]=tmp;
                }
                else {
                    int flag=0;
                    for (auto sum:twosum[numbers[i]+numbers[j]])
                        if (numbers[sum.first]==numbers[i] && numbers[sum.second]==numbers[j]
                           || numbers[sum.first]==numbers[j] && numbers[sum.second]==numbers[i]){
                            flag=1;
                            break;
                        }
                    if (flag==0) twosum[numbers[i]+numbers[j]].push_back(make_pair(i,j));
                }
            }

        for (int i=0;i<numbers.size()-1;i++){
            //不可用if (i!=0 && numbers[i]==numbers[i-1]) continue;
            //因为下面第一个位置的数可能不符合下面防止镜像解的条件而后面的位置可以,但是却被跳过了
            for (int j=i+1;j<numbers.size();j++) {
                //不可用 if (j!=i+1 && numbers[j]==numbers[j-1]) continue; 去重
                if (twosum.find(target-numbers[i]-numbers[j])!=twosum.end()) {
                    for (auto sum:twosum[target-numbers[i]-numbers[j]]) {
                        if (i>sum.first && i>sum.second) {//防止找到镜像解a+b+c+d和c+d+a+b
                            vector<int> tmp;
                            tmp.push_back(numbers[sum.first]);
                            tmp.push_back(numbers[sum.second]);
                            tmp.push_back(numbers[i]);
                            tmp.push_back(numbers[j]);
                            //查重
                            int flag=0;
                            for (int k=0;k<res.size();k++)
                               if (res[k]==tmp) {flag=1; break;}
                            if (flag==0) res.push_back(tmp);
                        }
                    }
                }
            }
        }
        return res;
    }
};

610. 两数和 - 差等于目标值
两数差,没说减数和被减数的关系,所以要统一用abs来判断。

class Solution {
public:
    vector<int> twoSum7(vector<int> &nums, int target) {
        vector<int> res;
        if (nums.size()==0) res;
        vector<pair<int,int>> m;
        for (int i=0;i<nums.size();i++) {
            m.push_back(make_pair(nums[i],i));
        }
        
        sort(m.begin(),m.end());
        int i=0;
        int j=1;
        while (j<m.size()) {
            if (i==j) j++;
            else if (abs(m[j].first-m[i].first)==abs(target)) {
                res.push_back(min(m[i].second+1,m[j].second+1));
                res.push_back(max(m[i].second+1,m[j].second+1));
                return res;
            }else if (abs(m[j].first-m[i].first)>abs(target)) i++;
            else j++;
        }
    }
};

 

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值