代码随想录算法训练营day56|583.两个字符串的删除操作72.编辑距离 剑指offer56-I,56-II

583.两个字符串的删除操作

题目链接

本题和不同的子序列的区别在于,本题两个字符串中的字符都可以进行删除了。下标减1就是模拟删除元素的操作。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int l1=word1.size();
        int l2=word2.size();
        vector<vector<int>> dp(l1+1,vector<int>(l2+1,0));
        for(int i=0;i<=l1;i++){
            dp[i][0]=i;
        }
        for(int j=0;j<=l2;j++){
            dp[0][j]=j;
        }
        for(int i=1;i<=l1;i++){
            for(int j=1;j<=l2;j++){
                if(word1[i-1]==word2[j-1]){//这写错了,造成溢出
                    dp[i][j]=dp[i-1][j-1];
                }else{
                    dp[i][j]=min(dp[i-1][j]+1,min(dp[i][j-1]+1,dp[i-1][j-1]+2));
                }
            }
        }
        return dp[l1][l2];
    }
};

还有一种方法是,求出最长公共子序列的长度,然后让两个字符串的总长度减去公共子序列长度*2。代码基本是一样的,就是最后返回值不一样。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int l1=word1.size();
        int l2=word2.size();
        vector<vector<int>> dp(l1+1,vector<int>(l2+1,0));
        for(int i=1;i<=l1;i++){
            for(int j=1;j<=l2;j++){
                if(word1[i-1]==word2[j-1]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }else{
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return l1+l2-dp[l1][l2]*2;
    }
};

72.编辑距离 

题目链接

题目中要求的是返回将 word1 转换成 word2 所使用的最少操作数,但其实我们对word1 word2 都操作也是可以的,Word1添加元素和Word2删除元素的操作次数都是一样的。也就是正向操作和逆向操作的次数是一样的。

主要还是明确dp数组的含义:dp[i][j]表示使以i-1为结尾的Word1和以j-1为结尾的Word2相同的最少操作次数。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int l1=word1.size();
        int l2=word2.size();
        vector<vector<int>> dp(l1+1,vector<int>(l2+1,0));
        for(int i=0;i<=l1;i++){
            dp[i][0]=i;
        }
        for(int j=0;j<=l2;j++){
            dp[0][j]=j;
        }
        for(int i=1;i<=l1;i++){
            for(int j=1;j<=l2;j++){
                if(word1[i-1]==word2[j-1]){//dp[i][j]的含义是可以使以i-1为结尾的word1,以j-1为结尾的word2相同的最少操作次数
                    dp[i][j]=dp[i-1][j-1];
                }else{
                    dp[i][j]=min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+1);
                }
            }
        }
        return dp[l1][l2];
    }
};

错因(错了好几遍了):没有明确dp数组的含义,比较的时候还是if(word1[i]==word2[j]),和dp[i][j]相对应的是word1[i-1]和word2[j-1]。

剑指 Offer 56 - I. 数组中数字出现的次数

题目链接

讲解思路参考:B站视频

提到元素出现的次数首先要想到哈希表,但此题要求空间复杂度是O(1),所以不能用哈希表,考虑用位运算。

先想一种最简单的情况,只有一个数出现了一次,其他数都出现了两次,那么所有元素异或的结果就是出现一次的元素,因为其他相同的元素异或时都抵消了。那如果有两个元素出现了一次,所有元素异或的结果就是这两个元素异或的结果,之后我们将它和1(不断左移)相与,找到从哪个二进制位开始不相同的(只需要找到最低位即可)。然后我们用这个结果和每一个元素相与来划分数组元素。因为这两个元素在这一位一定是不相同的,所以一定会被分为两个区间,而其他相同的元素一定会被分到同一个区间,那这时就又回到了最简单的情况了,我们只需要把两个区间所有的元素分别全部求异或即可得到最终想要的数据。

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int z=0;
        for(int num:nums){
            z^=num;
        }
        int m=1;
        while((z&m)==0){
            m<<=1;
        }
        int x=0,y=0;
        for(int num:nums){
            if((num&m)==0){//&的优先级低于==
                x^=num;
            }else{
                y^=num;
            }
        }
        return {x,y};
    }
};

错因(第二次出错):&的优先级低于==,所以要加()。

剑指 Offer 56 - II. 数组中数字出现的次数 II

题目链接

方法一:排序。先将数组元素排序,每三个相同的一定会聚在一起,重新遍历排序好的数组,以3倍的距离跳跃式遍历的同时,判断长度为3的区间内的数是否一样。出现一次的数的位置有两种情况,第一种是在三个相同的数前面,第二种是在整个数组的末尾。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size()-2;i+=3){
            if(nums[i]!=nums[i+1]) return nums[i];
        }
        return nums.back();
    }
};

方法二:哈希表。用unordered_map记录元素出现的次数,key是元素值,value是出现的次数。第一次遍历统计次数,第二次遍历返回value值为1的元素。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int,int> dic;
        for(int num:nums){
            dic[num]++;
        }
        for(int num:nums){
            if(dic[num]==1) return num;
        }
        return 0;
    }
};

方法三:位运算。二进制中每一位只能是0/1。我们将所有数当前位的二进制数累加,然后对3取余,余数就是我们要求的数的当前位的二进制数。因为只有一个数出现了一次,其他的数都是出现了三次,三个数的当前位的二进制数相加对3取余,结果一定是0,那假设我们要求的数是x,x的当前位要么是1要么是0,对3取余结果仍然是本身,1/0,所以这就求得最后的结果。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        vector<int> res(32,0);
        int m=1;
        int sum=0;
        for(int i=0;i<32;i++){
            for(int j=0;j<nums.size();j++){
                if((nums[j]&m)!=0){//错因:不是==0,只有第一次运算最低位的时候才是1,到第二位及以后只是判断当前位是不是1,但是nums[j]&m的结果就不是1了,比如0010
                    res[i]++;
                }
            }
            res[i]%=3;//因为其他三个相同的数的二进制位相加对3取余,结果一定是0,那剩下的就是出现一次的数的二进制位
            sum+=res[i]*m;
            if(m==0x80000000){
                break;
            }else{
                m<<=1;//左移一位,准备对第二位累加,取余,以此类推
            }
        }
        return sum;
    }
};

注意最后当m已经是2的31次方的时候再左移就会超出范围,所以我们应该判断一下,当m==0x80000000时就要break了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值