总的链接
面试经典 150 题 - 学习计划 - 力扣(LeetCode)全球极客挚爱的技术成长平台
88.合并两个有效数组
因为有序,直接设置双指针置于两个数组的末尾,从后往前直接模拟就好了,贪心的比较两个指针所指元素,优先挑出大的,放入答案数组中去;
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1 = m - 1, p2 = n - 1;
int tail = m + n - 1;
int cur;
while (p1 >= 0 || p2 >= 0) {
if (p1 == -1) {
cur = nums2[p2--];
} else if (p2 == -1) {
cur = nums1[p1--];
} else if (nums1[p1] > nums2[p2]) {
cur = nums1[p1--];
} else {
cur = nums2[p2--];
}
nums1[tail--] = cur;
}
}
};
27.移除元素
题意 : 在nums中原地移除值=val的数,然后返回新数组的长度;
思路 : 模拟即可,设置双指针,一个j指向新数组的尾下标,一个i指向原数组,一遍遍历,当nums[i]!=val时,将其加入新数组中,否则直接跳过即可;
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int n = nums.size() ;
int i=0,j=0;
for(;i<n;i++){
if(nums[i]!=val) nums[j++] = nums[i];
}
return j ;
}
};
26.删除有序数组中的重复项
思路 : 还是双指针模拟,思路与上题类似;
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int j=0;
int n = nums.size() ;
for(int i=0;i<n;i++){
int k = i + 1 ;
while(k<n && nums[k]==nums[i]) k ++;
nums[j++] = nums[i];
i = k - 1 ;
}
return j ;
}
};
80 . 删除有序数组中的重复项 II
思路 : 上一题多了一个保留两个重复元素,那么在枚举的过程中,发现重复的元素数量>=2,那么只将两个加入新数组即可;
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int j=0;
int n = nums.size() ;
for(int i=0;i<n;i++){
int k = i + 1 ;
while(k<n && nums[k]==nums[i]) k ++;
if(k-i>=2){
nums[j++] = nums[i];
nums[j++] = nums[i];
}else{
nums[j++] = nums[i];
}
i = k - 1 ;
}
return j ;
}
};
169.多数元素
法一(众数理论) :
思路 : 众数理论,由于在题目当中的众数定义为出现次数>=n/2的元素,那么在排好序之后,该元素其中的一个一定会出现在数组最中间 ;
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(),nums.end());
return nums[nums.size()/2];
}
};
法二 (哈希表暴力枚举):
思路 : 利用hash表统计每个元素出现次数,然后枚举哈希表,找到次数最大的元素;这里优化为边遍历边统计;
class Solution {
public:
int majorityElement(vector<int>& nums) {
int n = nums.size();
unordered_map<int,int> counts;
int cnt = 0;
int ma;
for(int num : nums){
++counts[num];
if(counts[num] > cnt){
ma = num;
cnt = counts[num];
}
}
return ma;
}
};
法三(摩尔投票法)
利用摩尔投票法,来解决此题,详情请看代码 :
class Solution {
public int majorityElement(int[] nums) {
int x = 0 , votes = 0 ;
for(int num : nums){
if(votes == 0) x = num ;
votes += num == x ? 1 : -1 ;
}
return x ;
}
}
189.轮转数组
思路 : 首先先复制nums数组到res中,保留元素组的情况,对于右移k位,那么也就是nums[(i+k)%n] = res[i] ,详细请看代码 :
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
vector<int> res(n);
for(int i=0;i<n;i++) res[i] = nums[i];
for(int i=0;i<n;i++){
nums[(k+i)%n] = res[i];
}
}
};
121 . 买卖股票的最佳时机
思路 : 一遍遍历,在遍历的过程中,用pre存前面的最小元素,那么对于第i天如果要卖股票的话,那么最大收益就是prices[i]-pre ; 一遍遍历,循环更新答案就好 ;
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size() ;
int mi = prices[0] ;
int ans = 0 ;
for(int i=1;i<n;i++){
ans = max (ans , prices[i] - mi);
mi = min(mi , prices[i]);
}
return ans ;
}
};
122 . 买卖股票的最佳时机 II
这题就直接遍历就好了,举个例子 :
3 1 5 , 那么ans = 5 - 1
1 2 4 , 那么ans = 4 - 1 = 4-2+2-1
用贪心的思路,相邻两个, 只要p[i]>p[i-1],那么就在前一天买,第i买,就会造成最优的结果 ;
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ans = 0;
for(int i=1;i<prices.size();i++){
int tmp = prices[i] - prices[i-1];
ans += tmp>0 ? tmp : 0;
}
return ans;
}
};
55.跳跃游戏
一遍遍历 ,记录当前能够达到的最远距离k,如果 i > k 表示到不了,直接返回false即可,在循环的过程中更新k即可;
class Solution {
public:
bool canJump(vector<int>& nums) {
int k = 0;
for(int i=0;i<nums.size();i++){
if(i>k) return false;
k = max(k,i+nums[i]);
}
return true;
}
};
45 . 跳跃游戏 II
题目 :
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
法一 (逆向) :
反方向查找能够跳到当前位置且距离当前位置最远的点;
class Solution {
public int jump(int[] nums) {
// 反方向查找能跳到当前位置且距离当前位置最远的距离
int p = nums.length - 1 ;
int ans = 0 ;
while(p>0){
for(int i=0;i<p;i++){
if(i+nums[i]>=p){
p = i ;
ans ++;
break;
}
}
}
return ans ;
}
}
法二 : (正向) :
法一逆向过来,能够优化到O(n)
class Solution {
public int jump(int[] nums) {
// 反方向查找能跳到当前位置且距离当前位置最远的距离
int p = nums.length - 1 ;
int ans = 0 ;
int ma = 0 ;
int r = 0;
for(int i=0;i<p;i++){
ma = Math.max(ma , i + nums[i]);
if(i==r){
r = ma ;
ans ++ ;
}
}
return ans ;
}
}
274 . H指数
直接贪心 :
先排序,从后往前,如果c[i] >= n-i , 那么表示 H = n - i 是可行的,在遍历的过程中更新答案即可 ;
class Solution {
public int hIndex(int[] c) {
int ans = 0 ;
int n = c.length ;
Arrays.sort(c);
for(int i=n-1;i>=0;i--){
if(c[i]>=n-i) {
ans = Math.max(ans , n-i);
}
}
return ans ;
}
}
380 . O(1)时间插入,删除和获取随机元素
思路 : 利用边长数组和哈希表实现,对于随机选取,利用rand函数随机生成一个数组下标,满足选取的随机性 ;
class RandomizedSet {
private:
vector<int> a ;
unordered_map<int,int> mp ;// key -> 值 , val -> 在a中的下标
public:
RandomizedSet() {
srand((unsigned)time(NULL));
}
bool insert(int val) {
if(mp.count(val)){
return false;
}
int idx = a.size() ;
a.push_back(val);
mp[val] = idx ;
return true ;
}
bool remove(int val) {
if(!mp.count(val)){
return false;
}
int idx = mp[val] ;
int last = a.back() ;
a[idx] = last ;
mp[last] = idx ;
mp.erase(val);
a.pop_back() ;
return true ;
}
int getRandom() {
int randIdx = rand() % a.size() ;
return a[randIdx];
}
};
/**
* Your RandomizedSet object will be instantiated and called as such:
* RandomizedSet* obj = new RandomizedSet();
* bool param_1 = obj->insert(val);
* bool param_2 = obj->remove(val);
* int param_3 = obj->getRandom();
*/
238 . 除自身以外数组的乘积
由于可能有0的出现,那么就否定了,先求整体的乘积,然后除以自身来得到答案的可能 ;
这里采用前后缀和的思想来解决 ;
class Solution {
public:
typedef long long LL;
vector<int> productExceptSelf(vector<int>& nums) {
int n = nums.size();
vector<LL> front(n,0);
vector<LL> back(n,0);
front[0]=1;
back[n-1]=1;
for(int i=1;i<n;i++) front[i]=nums[i-1]*front[i-1];
for(int j=n-2;j>=0;j--) back[j]=nums[j+1]*back[j+1];
vector<int> ans(n,0);
for(int i=0;i<n;i++) ans[i]=front[i]*back[i];
return ans;
}
};
134 . 加油站
本题关键在于 :
// 假设从x加油站出发经过z加油站最远能到达y加油站,
// 那么从z加油站直接出发,不可能到达y下一个加油站。
// 因为从x出发到z加油站时肯定还有存储的油,或者刚好用完,这都到不了y的下一站,
// 而直接从z出发刚开始是没有存储的油的,所以更不可能到达y的下一站。
// 所以如果 i 最远能够到达 j ,根据上边的结论 i + 1 到 j 之间的节点都不可能绕一圈了。
// 所以直接从 j + 1 开始考虑即可
// 想象成一个圆,所以 i 后边的节点就都不需要考虑了,直接返回 -1 即可。
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int n = gas.size() ;
for(int i=0;i<n;i++){
int j = i ;
int remain = gas[i] ;
while(remain-cost[j]>=0){
remain = remain - cost[j] + gas[(j+1)%n];
j = (j+1)%n;
if(j==i){
return i ;
}
}
if(j<i){
return-1 ;
}
i = j ;
}
return -1 ;
}
};
135.分发糖果
贪心 , 只用考虑左边和右边相邻且向中心递增且rating小于自己的个数 ;
class Solution {
public:
int candy(vector<int>& ratings) {
int n = ratings.size();
vector<int> left(n,1);
vector<int> right(n,1);
int ans = 0;
for(int i=1;i<n;i++){
if(ratings[i] > ratings[i-1]){
left[i] = left[i-1]+1;
}
}
for(int i=n-2;i>=0;i--){
if(ratings[i]>ratings[i+1]) right[i] = right[i+1]+1;
}
for(int i=0;i<n;i++){
ans += max(left[i],right[i]);
}
return ans;
}
};
42 . 接雨水
对于每个h[i],他上面能够存储的雨水的量只取决于min(L[i] , R[i])-h[i] ;其中L[i]指的是包含i在内及其左边所有柱子最高的高度,R[i]同理,指右边的,那么这里用前后缀数组来解决就行了 ;
class Solution {
public:
int trap(vector<int>& h) {
int n = h.size() ;
vector<int> l(n),r(n);
l[0] = h[0];
r[n-1] = h[n-1];
for(int i=1;i<n;i++) l[i] = max(l[i-1] , h[i]);
for(int i=n-2;i>=0;i--) r[i] = max(r[i+1] , h[i]);
int ans = 0 ;
for(int i=0;i<n;i++){
ans += min(l[i],r[i])-h[i];
}
return ans ;
}
};
13 . 罗马数字转整数 :
模拟题,直接模拟就好了!!!
import java.util.*;
class Solution {
public int romanToInt(String s) {
int sum = 0;
int preNum = getValue(s.charAt(0));
for(int i = 1;i < s.length(); i ++) {
int num = getValue(s.charAt(i));
if(preNum < num) {
sum -= preNum;
} else {
sum += preNum;
}
preNum = num;
}
sum += preNum;
return sum;
}
private int getValue(char ch) {
switch(ch) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return 0;
}
}
}
12 . 整数转罗马数字
上一题的逆运算,也是模拟;
class Solution {
int[] values = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
String[] symbols = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
public String intToRoman(int num) {
StringBuffer roman = new StringBuffer();
for (int i = 0; i < values.length; ++i) {
int value = values[i];
String symbol = symbols[i];
while (num >= value) {
num -= value;
roman.append(symbol);
}
if (num == 0) {
break;
}
}
return roman.toString();
}
}
58 . 最后一个单词的长度
先从后向前,找到第一个不是 ' ' 的下标,然后再往前遍历,遇到为' '就停下来,否则继续向前,每次向前,都++ans一次 ;
class Solution {
public int lengthOfLastWord(String s) {
int ans = 0;
int n = s.length()-1;
while(n>=0 && s.charAt(n)==' ') n--;
if(n<0) return 0 ;
while(n>=0 && s.charAt(n)!=' '){
n--;
ans ++;
}
return ans ;
}
}
14 . 最长公共前缀
思路 : 横向扫描,对于本题 : 可以得到以下结论 :
LCP(S1…Sn)=LCP(LCP(LCP(S1,S2),S3),…Sn)
那么只需遍历一次,每次sj都与之前的LCp(s1,s2,...sj-1)做LCp运算,在遍历的过程中如果发现结果已经是空串,直接返回""即可;
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs.length == 0 || strs == null) return "";
// 直接横向扫描
String s = strs[0];
//iterate each strs[i]
for(int i = 1; i < strs.length; i++)
{
s = findPrefix(s,strs[i]);
if(s.length() == 0) break;
}
return s;
}
public String findPrefix(String s1, String s2)
{
int min = Math.min(s1.length(), s2.length());
int j = 0;
while(j < min && s1.charAt(j) == s2.charAt(j))
{
j ++;
}
return s1.substring(0,j);
}
}
151 . 反转字符串中的单词
遇到一个反转一个,最后整体反转,就能够得到答案了;
class Solution {
public:
string reverseWords(string s) {
int n = s.size();
int k=0;
for(int i=0;i<n;i++){
int j=i;
while(j<n && s[j]==' ') j++;
if(j==n) break;
i=j;
while(j<n&&s[j]!=' ') j++;
reverse(s.begin()+i,s.begin()+j);
if (k) s[k ++ ] = ' ';
while (i < j) s[k ++ ] = s[i ++ ];
}
s.erase(s.begin()+k,s.end());
reverse(s.begin(),s.end());
return s;
}
};
6 . Z字形变换
模拟就好了
class Solution {
public:
string convert(string s, int numRows) {
string res;
if (numRows == 1) return s;
for (int j = 0; j < numRows; j ++ )
{
if (j == 0 || j == numRows - 1)
{
for (int i = j; i < s.size(); i += (numRows-1) * 2)
res += s[i];
}
else
{
for (int k = j, i = numRows * 2 - 1 - j - 1;
i < s.size() || k < s.size();
i += (numRows - 1) * 2, k += (numRows - 1) * 2)
{
if (k < s.size()) res += s[k];
if (i < s.size()) res += s[i];
}
}
}
return res;
}
};
28 . 找出字符串中第一个匹配项的下标
法一
直接模拟,遍历每一个位置,看是否与匹配项相等 ;
Java代码 :
class Solution {
public int strStr(String h, String n) {
int k = h.length() , m = n.length() ;
char[] s = h.toCharArray() , p = n.toCharArray() ;
for(int i=0;i<=k-m;i++){
int a = i , b = 0 ;
while(b<m && s[a]==p[b]){
a++;
b++;
}
if(b==m) return i ;
}
return -1 ;
}
}
法二 :
KMP算法 :
class Solution {
public:
int strStr(string s, string p) {
int n = s.size(), m = p.size();
if(m == 0) return 0;
//设置哨兵
s.insert(s.begin(),' ');
p.insert(p.begin(),' ');
vector<int> next(m + 1);
//预处理next数组
for(int i = 2, j = 0; i <= m; i++){
while(j and p[i] != p[j + 1]) j = next[j];
if(p[i] == p[j + 1]) j++;
next[i] = j;
}
//匹配过程
for(int i = 1, j = 0; i <= n; i++){
while(j and s[i] != p[j + 1]) j = next[j];
if(s[i] == p[j + 1]) j++;
if(j == m) return i - m;
}
return -1;
}
};
68 . 文本左右对齐
贪心 + 模拟
class Solution {
// blank 返回长度为 n 的由空格组成的字符串
string blank(int n) {
return string(n, ' ');
}
// join 返回用 sep 拼接 [left, right) 范围内的 words 组成的字符串
string join(vector<string> &words, int left, int right, string sep) {
string s = words[left];
for (int i = left + 1; i < right; ++i) {
s += sep + words[i];
}
return s;
}
public:
vector<string> fullJustify(vector<string> &words, int maxWidth) {
vector<string> ans;
int right = 0, n = words.size();
while (true) {
int left = right; // 当前行的第一个单词在 words 的位置
int sumLen = 0; // 统计这一行单词长度之和
// 循环确定当前行可以放多少单词,注意单词之间应至少有一个空格
while (right < n && sumLen + words[right].length() + right - left <= maxWidth) {
sumLen += words[right++].length();
}
// 当前行是最后一行:单词左对齐,且单词之间应只有一个空格,在行末填充剩余空格
if (right == n) {
string s = join(words, left, n, " ");
ans.emplace_back(s + blank(maxWidth - s.length()));
return ans;
}
int numWords = right - left;
int numSpaces = maxWidth - sumLen;
// 当前行只有一个单词:该单词左对齐,在行末填充剩余空格
if (numWords == 1) {
ans.emplace_back(words[left] + blank(numSpaces));
continue;
}
// 当前行不只一个单词
int avgSpaces = numSpaces / (numWords - 1);
int extraSpaces = numSpaces % (numWords - 1);
string s1 = join(words, left, left + extraSpaces + 1, blank(avgSpaces + 1)); // 拼接额外加一个空格的单词
string s2 = join(words, left + extraSpaces + 1, right, blank(avgSpaces)); // 拼接其余单词
ans.emplace_back(s1 + blank(avgSpaces) + s2);
}
}
};