Leetcode(上)
1.LeetCode01
两数之和
给定一个整数数组nums
和一个整数目标值target
,请你在该数组中找出和为目标值target
的那两个整数,并返回它们的数组下标。
- 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
- 你可以按任意顺序返回答案。
(1)暴力法
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int i, j;
for (i = 0; i < nums.size(); ++i) {
for (j = i + 1; j < nums.size(); ++j) {
if (nums[i] + nums[j] == target) {
return {i, j};
}
}
}
return {i, j};
}
};
(2)二分查找
思想:sort排序 + 二分查找
-
使用for循环遍历数组中的每一个数字
-
在for循环内部,使用target值减去当前遍历的数字值获取配对值ret,使用二分查找该数字ret
-
如果找到该数字则直接输出其下标(初始若有序的情况下)
-
实际是无序的!难点在于如何联系被sort打乱下标后的vec与未打乱的nums,并从其中找出目标数字的下标?
for (int j = 0; j < n; j++) { if (nums[j] == vec[i] || nums[j] == ret) result.push_back(j); if(result.size() == 2) return result; }
//自己写二分差查找
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> vec;
int n = nums.size();
//1.遍历nums将数据拷贝到vec中
for (auto &temp: nums) {
vec.push_back(temp);
}
//2.对vec进行排序
sort(vec.begin(), vec.end());
//3.开始二分查找
for (int i = 0; i < n; i++) {
//(1)获取配对值
int ret = target - vec[i];
//(2)查找配对值ret在vec中的下标lower_bound(array.begin(),array.end(),targetNumber)
//该方法返回一个指针,要获取下标只需将p与array.begin()做一次减法即可
int idx = lower_bound(vec.begin(), vec.end(), ret) - vec.begin();
//(3)在vec中找到了配对值ret对应的下标idx
if(0 <= idx && idx < n && vec[idx] == ret) {
vector<int> result;
//(4)获取配对值ret在nums中的下标(解决sort顺序打乱的问题)
for (int j = 0; j < n; j++) {
if (nums[j] == vec[i] || nums[j] == ret) result.push_back(j);
if(result.size() == 2) return result;
}
}
}
return {};
}
};
//利用stl提供的lower_bound二分查找
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> vec;
//1.遍历nums将数据拷贝到vec中
for (auto &temp: nums) {
vec.push_back(temp);
}
//2.对vec进行排序
sort(vec.begin(), vec.end());
//3.开始二分查找
for (int i = 0; i < vec.size(); ++i) {
//(1)获取配对值
int ret = target - vec[i];
//(2)二分查找配对值ret
int l = i + 1;
int r = vec.size() - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (ret == vec[mid]) {
//从有序的vec中找到对应在无序的nums中的下标
vector<int> result;
for (int j = 0; j < vec.size(); ++j) {
if (nums[j] == vec[i] || nums[j] == ret) result.push_back(j);
if(result.size() == 2) return result;
}
}
if (ret < vec[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
}
}
return {};
}
};
(3)双指针
思想:sort排序 + 双指针法
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> vec;
//1.遍历nums将数据拷贝到vec中
for (auto &temp: nums) {
vec.push_back(temp);
}
//2.对vec进行排序
sort(vec.begin(), vec.end());
//3.使用双指针法在有序vec容器中进行查找
int l = 0;
int r = vec.size() - 1;
while (l < r) {
int sum = vec[l] + vec[r];
if (sum == target) {
vector<int> result;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == vec[r] || nums[i] == vec[l]) result.push_back(i);
if(result.size() == 2) return result;
}
}
if (sum > target) {
--r;
} else {
++l;
}
}
return {};
}
};
(4)哈希表
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int,int> hashmap;
vector<int> result(2,-1);
for (int i = 0; i < nums.size(); i++) {
hashmap.insert(map<int, int>::value_type(nums[i], i));
}
for (int i = 0; i < nums.size(); i++) {
//判定是否找到目标元素 且目标元素不能是本身
if(hashmap.count(target - nums[i]) > 0 && (hashmap[target - nums[i]] != i)) {
result[0] = i;
result[1] = hashmap[target - nums[i]];
break;
}
}
return result;
};
};
哈希表优化(减少for循环次数):
- 在进行迭代将元素插入到表中的同时,就可以马上进行检查,表中是否已经存在当前元素所对应的目标元素
- 如果存在,那我们已经找到了对应解,并立刻将其返回
//哈希表优化
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int, int> hashmap;
vector<int> result(2,-1);
for(int i = 0; i < nums.size(); ++i) {
if (hashmap.count(target - nums[i]) > 0) {
//1.检查表中是否已经存在当前元素所对应的目标元素
result[0] = hashmap[target - nums[i]];
result[1] = i;
break;
} else {
//2.若不存在则再将下标与data反过来放入map中,以便用来获取下标
hashmap[nums[i]] = i;
}
}
return result;
};
};
2.LeetCode07
整数反转
给你一个 32 位的有符号整数 x ,返回其数字部分反转后的结果。
如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。
假设环境不允许存储 64 位整数(有符号或无符号)(只允许使用int类型的变量)
提示:-231 <= x <= 231 - 1
input:120
output:21
input:0
output:0
class Solution {
public:
int reverse(int x) {
int ans = 0;
while (x) {
if (ans > INT_MAX / 10 || ans < INT_MIN / 10 ||
ans == INT_MAX / 10 && x % 10 > 7 ||
ans == INT_MIN / 10 && x % 10 < -8
) {
ans = 0;
break;
}
ans = ans * 10 + x % 10;
x /= 10;
}
return ans;
}
};
3.LeetCode09
回文数
给你一个整数 x ,如果 x 是一个回文整数,返回 true;否则返回 false。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
提示:-231 <= x <= 231 - 1
(1)整数反转
利用整数反转的思想,来处理回文数字问题
class Solution {
public:
bool isPalindrome(int x) {
if (x < 0) return false;
long long ans = 0;
long long raw = x;
while (x) {
ans = ans * 10 + x % 10;
x /= 10;
}
return raw == ans;
}
};
(2)字符串反转
class Solution {
public:
bool isPalindrome(int x) {
if (x < 0) return false;
string raw = to_string(x);
string ans = raw;
for (int i = 0, j = raw.length() - 1; i < j; ++i, --j) {
char c = ans[i];
ans[i] = ans[j];
ans[j] = c;
}
return raw == ans;
}
};
4.LeetCode13
罗马数组转整数
罗马数字包含以下七种字符:I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII 而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值。同样地数字9表示为 IX。
这个特殊的规则只适用于以下六种情况:
- I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
- X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
- C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。
class Solution {
public:
int romanToInt(string s) {
int ans = 0;
for (int i = 0; i < s.length(); ++i) {
if (s[i] == 'I') {
//特殊判断
if (s[i + 1] == 'V' || s[i + 1] == 'X') {
ans += (s[i + 1] == 'V' ? 4 : 9);
i++;
} else {
ans += 1;
}
} else if (s[i] == 'V') {
ans += 5;
} else if (s[i] == 'X') {
//特殊判断
if (s[i + 1] == 'L' || s[i + 1] == 'C') {
ans += (s[i + 1] == 'L' ? 40 : 90);
i++;
} else {
ans += 10;
}
} else if (s[i] == 'L') {
ans += 50;
} else if (s[i] == 'C') {
//特殊判断
if (s[i + 1] == 'D' || s[i + 1] == 'M') {
ans += (s[i + 1] == 'D' ? 400 : 900);
i++;
} else {
ans += 100;
}
} else if (s[i] == 'D') {
ans += 500;
} else if (s[i] == 'M') {
ans += 1000;
} else {
return -1;
}
}
return ans;
}
};
5.LeetCode14
最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串。
//解法1:暴力循环 + substr字符串截取
/*runtime beats 73.15 % of cpp submissions
memory usage beats 27.86 % of cpp submissions (9 MB)
*/
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.size() == 0) return "";
string ans = strs[0];
for (int i = 1; i < strs.size(); ++i) {
int index = 0;
for (;index < max(strs[i].length(), ans.length()) && ans[index] == strs[i][index]; ++index);
ans = strs[i].substr(0, index);
}
return ans;
}
};
//解法2:暴力循环 + 字符串的+=操作(取消了对substr的引用,性能更优!)
/*runtime beats 73.15 % of cpp submissions
memory usage beats 92.63 % of cpp submissions (8.8 MB)
*/
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.size() == 0) return "";
string ans = strs[0];
string temp;
for (int i = 1; i < strs.size(); ++i) {
temp = ans;
ans = "";//避开新字符串长度更小的问题
for (int j = 0; j < strs[i].length() && j < temp.length(); ++j) {
if (strs[i][j] == temp[j]) {
ans += temp[j];
} else {
break;
}
}
}
return ans;
}
};
6.LeetCode26
删除有序数组中的重复项
不要使用额外的空间,你必须在 原地修改输入数组 并在使用 O(1) 额外空间的条件下完成。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if (nums.size() == 0) return 0;
int black = 0;
for (int red = 1; red < nums.size(); ++red) {
if (nums[red] != nums[black]) {
black++;
nums[black] = nums[red];
}
}
return black + 1;
}
};
7.LeetCode27
移除指定元素
(1)巧妙的移动元素
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int count = 0;
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] == val) {
count++;
} else {
nums[i - count] = nums[i];
}
}
return nums.size() - count;
}
};
(2)重复项删除类似
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int black = 0;
for (int red = 0; red < nums.size(); ++red) {
if (nums[red] != val) {
nums[black] = nums[red];
black++;
}
}
return black;
}
};
8.LeetCode35
搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
(1)暴力法
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] >= target) return i;
}
return nums.size();
}
};
(2)二分查找
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
if (nums[nums.size() - 1] < target) return nums.size();
int l = 0, r = nums.size() - 1;
while (l != r) {
int mid = (l + r) / 2;
if (nums[mid] >= target) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}
};
9.LeetCode38
外观数列
给定一个正整数 n,输出外观数列的第 n 项。
外观数列是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。你可以将其视作是由递归公式定义的数字字符串序列:
countAndSay(1) = "1"
countAndSay(n)
是对countAndSay(n-1)
的描述,然后转换成另一个数字字符串。
前五项如下:
1. 1
2. 11
3. 21
4. 1211
5. 111221
第一项是数字 1
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 "11"
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 "21"
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 "1211"
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 "111221"
要描述一个数字字符串,首先要将字符串分割为最小数量的组,每个组都由连续的最多相同字符组成。
对于每个组,先描述字符的数量然后描述字符,形成一个描述组。
- 要将描述转换为数字字符串,先将每组中的字符数量用数字替换,再将所有描述组连接起来。
- 例如,数字字符串
"3322251"
的描述如下图:
//遍历前一个字符串 得到后一个字符串
class Solution {
public:
void work(string &str, int cnt, char c) {
str += (char)(cnt + '0');//数字数量
str += c;//数字字符
}
void func(string &str1, string &str2) {
int cnt = 0;
for (int i = 0; i < str1.length(); ++i) {
if (cnt == 0 || str1[i] == str1[i - 1]) {
cnt++;
} else {
work(str2, cnt, str1[i - 1]);
cnt = 1;
}
}
work(str2, cnt, str1[str1.length() - 1]);
}
string countAndSay(int n) {
string ans[35] = {"", "1"};
for (int i = 2; i <= n; ++i) {
func(ans[i - 1], ans[i]);
}
return ans[n];
}
};
//利用斐波那契数列求解的思维 进行优化
class Solution {
public:
void work(string &str, int cnt, char c) {
str += (char)(cnt + '0');//数字数量
str += c;//数字字符
}
void func(string &str1, string &str2) {
int cnt = 0;
for (int i = 0; i < str1.length(); ++i) {
if (cnt == 0 || str1[i] == str1[i - 1]) {
cnt++;
} else {
work(str2, cnt, str1[i - 1]);
cnt = 1;
}
}
work(str2, cnt, str1[str1.length() - 1]);
}
string countAndSay(int n) {
string ans[2] = {"", "1"};
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
ans[a].clear();
func(ans[b], ans[a]);
swap(a, b);
}
return ans[b];
}
};
10.LeetCode20
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
(1)由简到繁
可以将问题先简化为1种括号的匹配问题(判断左括号、右括号的数量是否相等),再扩展括号匹配的种类:
+1
可以等价为进入,-1
可以等价为出去- 一对
()
可以等价为一个完整的事件 (())
可以看做事件与事件之间的完全包含关系
1种括号的匹配问题:
//写法1
bool isValid(char *s) {
int lum = 0, rnum = 0;
int len = strlen(s);
for (int i = 0; i < len; ++i) {
switch (s[i]) {
case '(' : ++lnum; break;
case ')' : ++rnum; break;
default : return flase;
}
if (lnum >= rnum) continue;
return false;
}
return lnum = rnum;
}
//写法2
bool isValid(char *s) {
int lum = 0;
int len = strlen(s);
for (int i = 0; i < len; ++i) {
switch (s[i]) {
case '(' : ++lnum; break;
case ')' : --rnum; break;
default : return flase;
}
if (lnum >= 0) continue;
return false;
}
return lnum = 0;
}
多种括号的匹配问题:
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
bool judgeOne(char *s, int *i, int d) {
bool flag = true;
int j = d;
while (s[*i] && flag) {
switch (s[*i]) {
case '(' :
++(*i);
flag = judgeOne(s, i, d + 1);
if (s[*i] == ')') {++(*i); flag &= true;}
else if (s[*i] == ']' || s[*i] == '}' || s[*i] == '\0') flag = false;
break;
case '[' :
++(*i);
flag = judgeOne(s, i, d + 1);
if (s[*i] == ']') {++(*i); flag &= true;}
else if (s[*i] == ')' || s[*i] == '}' || s[*i] == '\0') flag = false;
break;
case '{' :
++(*i);
flag = judgeOne(s, i, d + 1);
if (s[*i] == '}') {++(*i); flag &= true;}
else if (s[*i] == ')' || s[*i] == ']' || s[*i] == '\0') flag = false;
break;
case ')' :
case ']' :
case '}' :
return j = 0 ? false : true && flag;
default :
return false;
}
}
return flag;
}
bool isValid(char * s) {
int i = 0, len = strlen(s);
bool flag = true;
while (i < len && flag) {
flag &= judgeOne(s, &i, 0);
}
return flag;
}
int main() {
char s[1000];
cin >> s;
cout << s << " isValid : " << isValid(s) << endl;
return 0;
}
注意:该程序虽然可以判断成功,但是执行的时间复杂度太高导致超时。
(2)利用Stack解决:
#include <iostream>
using namespace std;
typedef struct Stack {
char *base;
int top;
int stackSize;
} Stack;
void InitStack(Stack &s, int n) {
s.base = new char[100000];
// s->base = (int *)malloc(sizeof(int) * n);
// s->base = malloc(stackSize);
s.stackSize = n;
s.top = -1;
};
bool EmptyStack(Stack &s) { return s.top == -1; }
void PushStack(Stack &s, char c) {
s.top++;
s.base[s.top] = c;
}
void PopStack(Stack &s) { s.top--; }
char GetTop(Stack &s) { return s.base[s.top]; }
bool isValid(string s) {
int len = s.length();
Stack stack;
InitStack(stack, len);
for (int i = 0; i < len; ++i) {
switch (s[i]) {
case '(':
case '[':
case '{':
PushStack(stack, s[i]);
break;
case ')':
if (EmptyStack(stack)) return false;
if (GetTop(stack) != '(') return false;
PopStack(stack);
break;
case '}':
if (EmptyStack(stack)) return false;
if (GetTop(stack) != '{') return false;
PopStack(stack);
break;
case ']':
if (EmptyStack(stack)) return false;
if (GetTop(stack) != '[') return false;
PopStack(stack);
break;
}
}
return EmptyStack(stack);
}
int main() {
char s[1000];
cin >> s;
cout << s << "isValid : " << isValid(s) << endl;
return 0;
}