*题目编号为Leetcode中对应的题号。
某位大佬的Leetcode题解参考链接
数组
- 二分查找
template<typename T>
int binarySearch(int arr[], int n, T target){
int l = 0, r = n - 1;// 在[l...r]范围内查找target
while(l <= r){// 当l==r时,[l...r]依然是有效的
int mid = l + (r - l) / 2;
if(arr[mid] == target)
return mid;
else if(arr[mid] > target)
r = mid - 1;// target在[l...mid-1]
else// arr[mid] < target
l = mid + 1;// target在[mid+1...r]
}
return -1;
}
- 要清楚地定义变量的含义,保证循环不变量
-
(283 移动零) 给定一个数组
nums
,编写一个函数将所有0
移动到数组的末尾,同时保持非零元素的相对顺序。示例:
输入: [0,1,0,3,12] 输出: [1,3,12,0,0]
说明:
- 必须在原数组上操作,不能拷贝额外的数组。
- 尽量减少操作次数。
// 暴力版
void moveZeroes(vector<int>& nums) {
vector<int> nums2;
for(int i=0;i<nums.size();i++){
if(nums[i])
nums2.push_back(nums[i]);
}
for(int i=0;i<nums2.size();i++){
nums[i]=nums2[i];
}
for(int i=nums2.size();i<nums.size();i++){
nums[i]=0;
}
}
// 优化版
void moveZeroes(vector<int>& nums){
int k=-1;// [0,k]为不含0的元素
for(int i=0;i<nums.size();i++){
if(nums[i])
if(i!=k)// 如果数组几乎不含0,即i==k,则不进行交换
swap(nums[i],nums[++k]);
else
k++;
}
}
-
(27 移除元素) 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
// 基础版
int removeElement(vector<int>& nums, int val) {
int k=-1;// [0,k]为不含val的元素
for(int i=0;i<nums.size();i++){
if(nums[i]!=val){
if(i!=k)
nums[++k]=nums[i];
else
k++;
}
}
return k+1;
}
// 优化版
int removeElement(vector<int>& nums, int val) {
int n=nums.size();
for(int i=0;i<n;){
if(nums[i]==val){
nums[i]=nums[--n];
}
else
i++;
}
return n;
}
-
(26 删除排序数组中的重复项) 给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。 示例 : 给定 nums = [0,0,1,1,1,2,2,3,3,4], 函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 你不需要考虑数组中超出新长度后面的元素。
// 基础版
int removeDuplicates(vector<int>& nums) {
vector<int> nums2;
int k=-1;
for(int i=0;i<nums.size();i++){
if(k==-1 || nums[i]!=nums2[k]){
nums2.push_back(nums[i]);
k++;
}
}
for(int i=0;i<=k;i++){
nums[i]=nums2[i];
}
return k+1;
}
// 优化版
int removeDuplicates(vector<int>& nums) {
int k=-1;// [0,k]为不含重复元素
for(int i=0;i<nums.size();i++){
if(k==-1 || nums[i]!=nums[k])
if(i!=k)
nums[++k]=nums[i];
else
k++;
}
return k+1;
}
-
(80 删除排序数组中的重复项II) 给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
示例 :
给定 nums = [1,1,1,2,2,3],
函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。
你不需要考虑数组中超出新长度后面的元素。
int removeDuplicates(vector<int>& nums) {
int k=-1;// [0,k]为符合要求的数组
int num=0;// nums[k]已经出现的次数
for(int i=0;i<nums.size();i++){
if(k==-1){// 第一个元素
k++;
num++;
}
else if(num==1&&nums[i]==nums[k]){// 未到2个
nums[++k]=nums[i];
num++;
}
else if(nums[i]!=nums[k]){// 新元素
nums[++k]=nums[i];
num=1;
}
}
return k+1;
}
-
(75 颜色分类、计数排序) 给定一个有n个元素的数组,数组中元素的取值只有0,1,2三种可能,为这个数组排序
示例:
输入: [2,0,2,1,1,0] 输出: [0,0,1,1,2,2]
// 计数排序
void sortColors(vector<int>& nums) {
int count[3]={0};// 存放0,1,2三个元素的个数
for(int i=0;i<nums.size();i++){
assert(nums[i]>=0 && nums[i]<=2);
count[nums[i]]++;
}
int index=0;
for(int i=0;i<sizeof(count)/sizeof(int);i++)
for(int j=0;j<count[i];j++)
nums[index++]=i;
}
// 三路快排
void sortColors(vector<int>& nums) {
int k1=-1;// [0,k1]==0
int k2=nums.size();// [k2,n-1]==2
for(int i=0;i<k2;){
if(nums[i]==1)
i++;
else if(nums[i]==0){
swap(nums[i++],nums[++k1]);
}
else{
assert(nums[i]==2);
swap(nums[i],nums[--k2]);
}
}
}
-
(215数组中的第K个最大元素) 在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 输出: 4
int findKthLargest(vector<int>& nums, int k) {
int index=-1;
int l=0, r=nums.size()-1;// 在[l,r]范围内找
while(l<=r){
index=partition(nums, l, r);
if(index<(nums.size()-k)){
l=index+1;
}
else if(index>(nums.size()-k)){
r=index-1;
}
else
return nums[index];
}
return -1;
}
// 对nums[l,r]部分进行partition
// 返回p,使得nums[l,p-1]<nums[p],nums[p+1,r]>nums[p]
int partition(vector<int>& nums, int l, int r){
int target=nums[l];
// nums[l+1,j]<target; nums[j+1,i)>target
int j=l;
for(int i=l+1;i<=r;i++){
if(nums[i]<target)
swap(nums[i],nums[++j]);
}
// nums[l,j-1]<target; nums[j+1,r]>target
swap(nums[l],nums[j]);
return j;
}
-
(167 两数之和II-输入有序数组) 给定一个已按照升序排列的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
// 基础版:二分搜索
// 其中一个数遍历整个数组,两一个数在剩余的数组中二分搜索
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int> ret;
for(int i=0;i<numbers.size()-1;i++){
int searchTarget=target-numbers[i];
int idx2=binarySearch(numbers, i+1, numbers.size()-1, searchTarget);
if(idx2!=-1){
int idx[2]={i+1,idx2+1};
ret=vector<int>(idx, idx+2);
}
}
return ret;
}
int binarySearch(vector<int>& nums, int l, int r, int target){
while(l<=r){
int mid=l+(r-l)/2;
if(nums[mid]>target)
r=mid-1;
else if(nums[mid]<target)
l=mid+1;
else
return mid;
}
return -1;
}
// 优化版:对撞指针
vector<int> twoSum(vector<int>& numbers, int target) {
int j=0, k=numbers.size()-1;
vector<int> ret;
while(j<k){
int sum=numbers[j]+numbers[k];
if(sum<target)
j++;
else if(sum>target)
k--;
else{
ret.push_back(++j);
ret.push_back(++k);
break;
}
}
return ret;
}
-
(125 验证回文串) 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
**说明:**本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama" 输出: true
示例 2:
输入: "race a car" 输出: false
bool isPalindrome(string s) {
int l=0, r=s.size()-1;
while(l<r){
if(!isalnum(s[l])){
l++;
continue;
}
if(!isalnum(s[r])){
r--;
continue;
}
if(tolower(s[l++])!=tolower(s[r--]))
return false;
}
return true;
}
-
(344 反转字符串) 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。
示例 2:
输入:["H","a","n","n","a","h"] 输出:["h","a","n","n","a","H"]
void reverseString(vector<char>& s) {
int l=0, r=s.size()-1;
while(l<r){
swap(s[l++],s[r--]);
}
}
-
(345 反转字符串中的元音字母) 编写一个函数,以字符串作为输入,反转该字符串中的元音字母。
示例 :
输入: "leetcode" 输出: "leotcede"
说明:
元音字母不包含字母"y"。
string reverseVowels(string s) {
int l=0, r=s.size()-1, i=0;
string ret_s(s);
while(l<r){
if(!isaeiou(s[l])){
ret_s[l]=s[l];
l++;
continue;
}
if(!isaeiou(s[r])){
ret_s[r]=s[r];
r--;
continue;
}
ret_s[l]=s[r];
ret_s[r]=s[l];
l++;
r--;
}
return ret_s;
}
bool isaeiou(char s){
if(s=='A'||s=='E'||s=='I'||s=='O'||s=='U'||
s=='a'||s=='e'||s=='i'||s=='o'||s=='u')
return true;
return false;
}
-
(11 盛最多水的容器) 给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明: 你不能倾斜容器,且 n 的值至少为 2。
示例:
输入:[1,8,6,2,5,4,8,3,7] 输出:49
int maxArea(vector<int>& height) {
int l=0, r=height.size()-1;
int maxarea=0;
while(l<r){
int V=(r-l)*min(height[l],height[r]);
if(V > maxarea)
max=V;
if(height[l]>height[r])
r--;
else
l++
}
return max;
}
-
子数组:一般来说不一定连续
-
一旦使用[]对数组取值时,就要保证是否越界
-
(209 长度最小的子数组) 给定一个含有 n 个正整数的数组和一个正整数**s **,找出该数组中满足其和 ≥ s 的长度最小的连续子数组。如果不存在符合条件的连续子数组,返回 0。
示例:
输入: s = 7, nums = [2,3,1,2,4,3] 输出: 2 解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
进阶:
如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。
滑动窗口:
首先计算滑动窗口nums[i,j]内元素的和是否大于s,若否,则j++,直到sum>s
此时,尝试让i++,直到sum<s,记录最短子数组的长度
int minSubArrayLen(int s, vector<int>& nums) {
int l=0, r=-1;// nums[l,r]为滑动窗口
int sum=0;
int len=nums.size()+1;// 之后会逐渐取最小值
while(l<nums.size()){
if(r+1<nums.size() && sum<s)// 对右边界判断是否越界
sum+=nums[++r];
else
sum-=nums[l++];
if(sum>=s)
len=min(len,r-l+1);
}
if(len==nums.size()-1)
return 0;
return len;
}
-
(剑指offer48 最长不含重复字符的子字符串) 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb" 输出: 1 解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
int lengthOfLongestSubstring(string s) {
int l=0, r=-1;// [l,r]为无重复字符的子串
bool ascii[256]={false};// 标记子串中含有的字符
int maxlen=0;// 最长子串的长度
while(l<s.size()){
if(r+1<s.size() && ascii[s[r+1]]==false)// 子串中不含有下一个字符
ascii[s[++r]]=true;
else// 子串中含有下一个字符
ascii[s[l++]]=false;
maxlen=max(maxlen,r-l+1);
}
return maxlen;
}
-
(438 找到字符串中所有字母异位词) 给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
示例 1:
输入: s: "cbaebabacd" p: "abc" 输出: [0, 6] 解释: 起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。 起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。
vector<int> findAnagrams(string s, string p) {
int ascii_s[26] = { false };
int ascii_p[26] = { false };
vector<int> ret;
int l = 0, r = -1;
for (char c : p)
ascii_p[c - 'a'] ++;
while (r + 1 < s.size()) {
r++;
ascii_s[s[r] - 'a'] ++;
if (r - l + 1 > p.size())
ascii_s[s[l++] - 'a'] --;
if (r - l + 1 == p.size() && same(ascii_s, ascii_p))
ret.push_back(l);
}
return ret;
}
bool same(int ascii_s[26], int ascii_p[26]) {
for (int i = 0; i < 26; i++)
if (ascii_s[i] != ascii_p[i])
return false;
return true;
}
-
(76 最小覆盖子串) 给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。
示例:
输入: S = "ADOBECODEBANC", T = "ABC" 输出: "BANC"
说明:
- 如果 S 中不存这样的子串,则返回空字符串
""
。 - 如果 S 中存在这样的子串,我们保证它是唯一的答案。
- 如果 S 中不存这样的子串,则返回空字符串
string minWindow(string s, string t) {
int l = 0, r = -1;// Searching ing [l,r]
vector<int> ascii_t(256, 0);// 记录目标字符串中每个字母的个数
vector<int> ascii_s(256, 0);// 记录待查字符子串中每个字母的个数
int minLen = s.size() + 1;
int start = -1, end = -1;// 记录包含子串的起始和终止位置
int count = 0;// 记录子串中包含有多少t中的字符
if (s.size() < t.size())
return string("");
for (char c : t)
ascii_t[c]++;
while (l < s.size()) {
if (count == t.size()) {
if ((r - l + 1) < minLen) {
minLen = r - l + 1;
start = l, end = r;
}
if (ascii_s[s[l]] <= ascii_t[s[l]])
count--;
ascii_s[s[l]]--;
l++;
}
else if (r + 1 < s.size()) {
if (ascii_s[s[r + 1]] < ascii_t[s[r + 1]])
count++;
ascii_s[s[r + 1]]++;
r++;
}
else
break;
}
if (start == -1)
return string("");
string ret = s.substr(start, end - start + 1);
return ret;
}