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+")");
}
}
};