week 2

文章主要介绍了几种编程算法,包括基于字符串的字母组合生成、三元组求和、链表操作如删除节点、两两交换节点、查找子串,以及数组操作如二分查找旋转数组中的特定值、最大面积容器等。还讨论了动态规划解决硬币组合问题和编辑距离算法。最后提到了生成有效括号的递归方法。
摘要由CSDN通过智能技术生成

7月6日

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:

输入:digits = ""
输出:[]
示例 3:

输入:digits = "2"
输出:["a","b","c"]

提示:

0 <= digits.length <= 4
digits[i] 是范围 ['2', '9'] 的一个数字。

其实就是树的深度遍历,这个流程图很烂,可忽略。

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        
        unordered_map<char,string> number{
            {'2',"abc"},
            {'3',"def"},
            {'4',"ghi"},
            {'5',"jkl"},
            {'6',"mno"},
            {'7',"pqrs"},
            {'8',"tuv"},
            {'9',"wxyz"}
        };

        vector<string> result;
        if(digits.empty()) return result;
        string temp;
        back(result,temp,digits,number,0);
        return result;       
    }
    void back(vector<string>& result, string& temp,const string& digits,const unordered_map<char,string> number,int deep ){
        if(deep==digits.size()) result.push_back(temp);
        else{
            char digit=digits[deep];
            for(char a:number.at(digit))
            {
                temp.push_back(a);
                back(result,temp,digits,number,deep+1);
                temp.pop_back();
            }
        }
        
    }
};

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

3 <= nums.length <= 3000
-105 <= nums[i] <= 105

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int length = nums.size();
        vector<vector<int>> total0;
        int left,right;
        sort(nums.begin(),nums.end());
        for(int i=0;i<length-2;++i)
        {
            right = length -1;
            if(i>0 && nums[i]==nums[i-1]) continue;
            else{
                for(left = i+1;left<length-1;++left)
                {
                    if(left>i+1&&nums[left]==nums[left-1]) continue;

                    while(left<right&&nums[i]+nums[left]+nums[right]>0) --right;
                    if(left==right) break;
                    if(nums[i]+nums[left]+nums[right]==0) {
                        total0.push_back({nums[i],nums[left],nums[right]});
                    }

                }             
            }
        }
        return total0;
    }
    
};

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点

示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:

输入:head = [1], n = 1
输出:[]
示例 3:

输入:head = [1,2], n = 1
输出:[1]

提示:

链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode *wei=head;//尾指针
        ListNode *zhang=head;//首指针
        while(n--) {wei=wei->next;}
        while(wei&&wei->next!=nullptr)
        {
            zhang=zhang->next;
            wei=wei->next;
        }
        if(wei==nullptr) return head->next;
        ListNode *temp=zhang->next;
        zhang->next=temp->next;
        delete temp;
        return head;
    }
};

7月7日

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:

输入:head = []
输出:[]
示例 3:

输入:head = [1]
输出:[1]

提示:

链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100

创建空节点作为头结点真的好用,前几题尝试不带头结点的去找,头都炸了。

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode *dummp=new ListNode(0,head);//创建一个头结点
        ListNode *now=dummp;
        while(now->next&&now->next->next)
        {
           ListNode *zhong=now->next;//中间指针
           ListNode *wei=now->next->next;//尾指针
           now->next=wei;
           zhong->next=wei->next;
           wei->next=zhong;
           now=zhong;
        }
        
        return dummp->next;
    }
};

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回  -1 。

示例 1:

输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:

输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

提示:

1 <= haystack.length, needle.length <= 104
haystack 和 needle 仅由小写英文字符组成

 其实就是暴力求解。

class Solution {
public:
    int strStr(string haystack, string needle) {
        int k=0;//主串下标
        if(haystack.size()<needle.size()) return -1;
        while(k<haystack.size()-needle.size()+1)
        {
            int i=k;//临时的主串下标
            int j=0;
            while(j<needle.size())
            {
                if(haystack[i]==needle[j]){i++;j++;}
                else{
                    i++;
                    break;
                }
                if(j==needle.size()) {return i-j;} 
            }
            k++;
        }
        return -1;
    }
};

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 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

提示:

n == height.length
2 <= n <= 105
0 <= height[i] <= 104

class Solution {
public:
    int maxArea(vector<int>& height) {
        int maxvolum=0;//记录最大容量
        int left=0;//记录左右边界指针
        int right=height.size()-1;
        while(left<right)
        {
            int volum = min(height[left],height[right])*(right-left);
            maxvolum = max(maxvolum,volum);
            if(height[left]<=height[right]) {++left;}
            else{--right;}
        }
        return maxvolum;
    }
};

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
示例 2:

输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
示例 3:

输入:nums = [1], target = 0
输出:-1

提示:

1 <= nums.length <= 5000
-104 <= nums[i] <= 104
nums 中的每个值都 独一无二
题目数据保证 nums 在预先未知的某个下标上进行了旋转
-104 <= target <= 104

采用二分查找算法。把数组nums对半分成两个数组,一个肯定有序,另一个可能有序也可能无序。(数组的首部和尾部顺序,则数组顺;首尾逆序,则数组无序)

当target在有序数组中,继续对半分,直到找到target和mid值一样。

当target在无序数组中,同样对半分。此时,该无序数组同样被分为两部分,一个有序,一个可能有序、可能无序。

如此循环,直到找到目标。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int length = nums.size();
        if(length==0) return-1;
        if(length==1){
            if(nums[0]==target){return 0;}
            else return -1;
        }
        int left=0;//左右指针
        int right=nums.size()-1;
        while(left<=right)
        {
            int mid=(left+right)/2;
            if(nums[mid]==target) return mid;
            if(nums[left]<=nums[mid])//判断是否有序数组
            {
                if(nums[left]<=target&&target<nums[mid]) {right=mid-1;}
                else{left=mid+1;}
            }
            else{
                if(nums[mid]<target&&target<=nums[right]) {left=mid+1;}
                else{right=mid-1;}
            }
        }
        return -1;
    }
};

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例 2:

输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
示例 3:

输入:nums = [], target = 0
输出:[-1,-1]

提示:

0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109

本题同样采用二分查找,找到一个target之后,对target左边的部分继续二分,直到找到最左边的target,即左边界。对第一次找到的target右边继续二分,直到找到最右边等等target,即右边界。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if(nums.size()==1){
            if(nums[0]==target) return vector<int>{0,0};
            else return vector<int>{-1,-1};
        }
        vector<int> result={-1,-1};
        int left=0;//左右指针
        int right=nums.size()-1;
        while(left<=right)//寻找左目标值
        {
            int mid=(left+right)/2;
            if(nums[mid]==target) {result[0]=mid;right=mid-1;}
            else if(nums[mid]>target) {right=mid-1;}
            else{left=mid+1;}
        }
        left=0;
        right=nums.size()-1;
        while(left<=right)//寻找右目标值
        {
            int mid=(left+right)/2;
            if(nums[mid]==target) {result[1]=mid;left=mid+1;}
            else if(nums[mid]>target) {right=mid-1;}
            else{left=mid+1;}
        }
        return result;
    }
};

7月8日

给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求 不使用 乘法、除法和取余运算。

整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8 ,-2.7335 将被截断至 -2 。

返回被除数 dividend 除以除数 divisor 得到的 商 。

注意:假设我们的环境只能存储 32 位 有符号整数,其数值范围是 [−231,  231 − 1] 。本题中,如果商 严格大于 231 − 1 ,则返回 231 − 1 ;如果商 严格小于 -231 ,则返回 -231 。

示例 1:

输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = 3.33333.. ,向零截断后得到 3 。
示例 2:

输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = -2.33333.. ,向零截断后得到 -2 。

提示:

-231 <= dividend, divisor <= 231 - 1
divisor != 0

思想:问题的本质就是 找到一个数x,使得x和除数相乘 无限逼近被除数。

刚开始是使用减法,一个一个的去减,但是运行超时。

所以使用二分法查找数x 通过快速乘算法来判断x*除数是否接近被除数dividend

class Solution {
public:
    int divide(int dividend, int divisor) {
        // 考虑被除数为最小值的情况
        if (dividend == INT_MIN) {
            if (divisor == 1) {
                return INT_MIN;
            }
            if (divisor == -1) {
                return INT_MAX;
            }
        }
        // 考虑除数为最小值的情况
        if (divisor == INT_MIN) {
            return dividend == INT_MIN ? 1 : 0;
        }
        // 考虑被除数为 0 的情况
        if (dividend == 0) {
            return 0;
        }
        
        // 一般情况,使用二分查找
        // 将所有的正数取相反数,这样就只需要考虑一种情况
        bool rev = false;
        if (dividend > 0) {
            dividend = -dividend;
            rev = !rev;
        }
        if (divisor > 0) {
            divisor = -divisor;
            rev = !rev;
        }

        // 快速乘
        auto quickAdd = [](int y, int z, int x) {
            // x 和 y 是负数,z 是正数
            // 需要判断 z * y >= x 是否成立
            int result = 0, add = y;
            while (z) {
                if (z & 1) {//一个阶段的加法
                    // 需要保证 result + add >= x
                    if (result < x - add) { return false;}
                    result += add;
                }
                if (z != 1) {//加数递增一倍
                    // 需要保证 add + add >= x
                    if (add < x - add) { return false; }
                    add += add;
                }
                z >>= 1;// 不能使用除法
            }
            return true;
        };
        
        int left = 1, right = INT_MAX, ans = 0;
        while (left <= right) {
            // 注意溢出,并且不能使用除法
            int mid = left + ((right - left) >> 1);
            bool check = quickAdd(divisor, mid, dividend);
            if (check) {
                ans = mid;
                // 注意溢出
                if (mid == INT_MAX) { break; }
                left = mid + 1;
            }
            else { right = mid - 1;}
        }
        return rev ? -ans : ans;
    }
};

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1
示例 2:

输入:coins = [2], amount = 3
输出:-1
示例 3:

输入:coins = [1], amount = 0
输出:0

提示:

1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104

思路:采用自底向上的动态规划算法。(自顶向下的动态规划需要递归,我理解起来很吃力,所以直接用简洁明了的自底向上方法)

定义了一个数组dp,记录金额为0所需要的硬币数,金额为1所需要的硬币数等等。

初始化定义dp[0]为0 在向上延伸过程中,dp[j]记录下可达并且硬币数最小的值。最终得到目标金额dp[amount]。如果最终金额认为自定义的特殊值amount+1,说明没有一条可达的路径,即失败。

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, amount+1);
        dp[0]=0;
        for(int j=0;j<=amount;j++)
        {
            for(int coin:coins)
            {
                if(j-coin<0) continue;//某一路径不可达
                dp[j]=min(dp[j],1+dp[j-coin]);//比较该路径和之前的路径哪个更小,取小
            }
        }
        return (dp[amount]==amount+1)? -1:dp[amount];
    }
};

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数  。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

示例 1:

输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:

输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')

提示:

0 <= word1.length, word2.length <= 500
word1 和 word2 由小写英文字母组成

 思路:同样是自底向上的动态规划,dp[i][j]指第i个字符修改成第j个字符所需要的的最小次数。当两个遍历到的字符相同,不做修改,继承上一组字符的次数(dp[i][j]=dp[i-1][j-1])

当字符不同时,不管是删除、插入和替换,都得修改一次,所以+1。对于删除,word1的字符指针往前移动一位,dp[i][j]需要修改的次数从dp[i-1][j]上继承+1。同理,插入和替换如下代码所示。

最终得到整个字符串的修改次数。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int m=word1.size();
        int n=word2.size();
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));
        for(int i=0;i<=m;i++){dp[i][0]=i;}
        for(int j=0;j<=n;j++){dp[0][j]=j;}
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;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],min(dp[i][j-1],dp[i-1][j-1]))+1;
                }
            }
        }
        return dp[m][n];
     }
};

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:

输入:n = 1
输出:["()"]

提示:

1 <= n <= 8

思路:其实就是利用递归暴力求解。

满足要求的括号序列:1、左括号个数<=n   2、左括号个数>=右括号个数

所以利用递归法枚举时,遇到不符合以上条件的直接跳过,节省时间。

class Solution {
public:
    vector<string> result;
    vector<string> generateParenthesis(int n) {
        dfs(0,0,n,"");//左括号数,右括号数,n,初始字符串
        return result;
    }
    void dfs(int left,int right,int n,string str)
    {
        if(left==n&&right==n) result.push_back(str);
        else{
            if(left<n) dfs(left+1,right,n,str+"(");
            if(left>right) dfs(left,right+1,n,str+")");
        }
    }
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值