前言
这两天把Leetcode的 Top-100-liked-questions - easy题目做了。
下面就记一下思路和代码,当作记录。
可能因为刚上手吧,感觉也不简单。
当然先记一下探索出来的刷Leetcode的姿势。
这样的话,这个星期暂且做不了多少题。先把上面的事做了吧!
Have a good WEEK!
贴一下列表
Title | Solution | Acceptance | Difficulty | Frequency | ||
---|---|---|---|---|---|---|
1 | Two Sum | 45.2% | Easy | |||
20 | Valid Parentheses | 38.2% | Easy | |||
21 | Merge Two Sorted Lists | 51.7% | Easy | |||
53 | Maximum Subarray | 45.9% | Easy | |||
70 | Climbing Stairs | 46.3% | Easy | |||
101 | Symmetric Tree | 45.8% | Easy | |||
104 | Maximum Depth of Binary Tree | 64.4% | Easy | |||
121 | Best Time to Buy and Sell Stock | 49.6% | Easy | |||
136 | Single Number | 63.3% | Easy | |||
141 | Linked List Cycle | 39.9% | Easy | |||
155 | Min Stack | 41.4% | Easy | |||
160 | Intersection of Two Linked Lists | 38.4% | Easy | |||
169 | Majority Element | 56.2% | Easy | |||
198 | House Robber | 41.6% | Easy | |||
206 | Reverse Linked List | 60.2% | Easy | |||
226 | Invert Binary Tree | 62.1% | Easy | |||
234 | Palindrome Linked List | 38.2% | Easy | |||
283 | Move Zeroes | 56.5% | Easy | |||
437 | Path Sum III | 45.1% | Easy | |||
448 | Find All Numbers Disappeared in an Array | 55.3% | Easy | |||
543 | Diameter of Binary Tree | 48.3% | Easy | |||
581 | Shortest Unsorted Continuous Subarray | 30.7% | Easy | |||
617 | Merge Two Binary Trees | 73.0% | Easy |
刷Leetcode的姿势
正常来说,是直接在Leetcode网页直接写代码。
虽然说有playground可以调试,但是还是本地调试比较舒服。
只需要把playground的代码放在vs就好了,再重定义一下输入 <input.txt
也要关掉SDL安全检查,不然得用scanf_s换scanf。
但是要附上以下的头文件
#include <vector>
#include <algorithm>
#include <stdio.h>
#include <ctype.h>
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
1. 两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
定义字典,key为num,value为index.
遍历添加num,但是每次添加都检查一下target - num是否存在。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int,int>M;
for (int i = 0;i<nums.size();i++){
if ( M.find(target-nums[i])!=M.end() ){
return { M[target-nums[i]],i};
}
M[nums[i]] = i;
}
return {};
}
};
20. 有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
定义一个stack,如果遇到( { [ 就直接压栈,遇到右边的就看看栈顶有没有相应的匹配元素,没有就报错。
最后栈不为空也报错。
class Solution {
public:
bool isValid(string s) {
int len = s.length();
if (len == 0) return true;
if (len == 1) return false;
stack<char> S;
for (int i = 0; i < len; i++) {
switch (s[i]) {
case ')':
if (S.empty()) return false;
if (S.top() == '(') {
S.pop();
break;
}
else return false;
case '}':
if (S.empty()) return false;
if (S.top() == '{') {
S.pop();
break;
}
else return false;
case ']':
if (S.empty()) return false;
if (S.top() == '[') {
S.pop();
break;
}
else return false;
default:
S.push(s[i]);
}
}
if (!S.empty()) return false;
else return true;
}
};
21. 合并两个有序链表
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
递归。
- 如果l1到尾了,就返回剩下的l2。
- 如果l2到尾了,就返回剩下的l1。
- 如果都没有到尾,小的list的next指向大的list。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(!l1) return l2;
if (!l2) return l1;
if(l1->val<l2->val){
l1-> next = mergeTwoLists(l1->next,l2);
return l1;
}else{
l2-> next = mergeTwoLists(l1,l2->next);
return l2;
}
}
};
53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
动态规划。
对于第i个值而言,要么是接上上一个连续子数组,要么不接,比较一下两种方式的值,取大的方式。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector <int> dp;
for (int i = 0; i<nums.size();i++){
//先放第一个
if(i==0){
dp.push_back(nums[i]);
continue;
}
int max = nums[i];
if( max +dp[i-1] >max )
max=max +dp[i-1];
dp.push_back(max);
}
return *max_element(dp.begin(),dp.end());
}
};
70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
入门题了。
动态规划。对于第k个台阶而言,达到本台阶只有两种来源:一是来自k-1级,一是来自k-2级。
class Solution {
public:
int climbStairs(int n) {
if (n==1 || n==2)return n;
int dp[1001];
dp[1] = 1;
dp[2] = 2;
for (int i = 3;i<=n;i++)
dp[i] = dp[i-2]+ dp[i-1];
return dp[n];
}
};
101. 对称二叉树
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
说明:
如果你可以运用递归和迭代两种方法解决这个问题,会很加分。
递归。不简单。
首先一棵树是对称的话,镜面位置的NULL和非NULL都是一样的。
所以
-
如果都为NULL,就返回true
-
如果只有一个为NULL,另一个不为NULL,就返回false
-
最终的表达式:当前值相等 且 左子树的值相等 且 有右子树的值相等
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(root==NULL)return true;
return isSymmetricHelp(root->left,root->right);
}
bool isSymmetricHelp(TreeNode* l,TreeNode* r){
if( (!l && !r)) return true;
else if ( !l || !r ) return false;
return ( l->val == r->val) && ( isSymmetricHelp(l->left,r->right) ) && ( isSymmetricHelp(l->right,r->left) );
}
};
104. 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
老题了。
递归。每次往上返回的值 = max(左子树返回值,右子树返回值) + 1
class Solution {
public:
int maxDepth(TreeNode* root) {
if(!root)return 0;
else return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};
121. 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
简单的动态规划。
每次记录累计的股票价格的最低值。然后计算在这个最低值情况下,当天卖出的利润,如果利润比之前的利润有上升,就sold。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int inf = 1e9;
int minprice = inf, maxprofit = 0;
for (int price: prices) {
maxprofit = max(maxprofit, price - minprice);
minprice = min(price, minprice);
}
return maxprofit;
}
};
136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
异或求不同。
141. 环形链表
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
虽然入门书可以用拓扑排序判断是否有环。
这里,用快慢指针判断是否有环更方便。
- 如果无环,快指针很快就可以到终点。
- 如果有环,肯定会有快指针 = 慢指针的时候。
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast!=NULL && fast->next!=NULL){
fast = fast->next->next;
slow = slow->next;
if(fast == slow){
return true;
}
}
return false;
}
};
155. 最小栈
设计一个支持 push,pop,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) – 将元素 x 推入栈中。
pop() – 删除栈顶的元素。
top() – 获取栈顶元素。
getMin() – 检索栈中的最小元素。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
定义两个栈就好了。
160. 相交链表
编写一个程序,找到两个单链表AB相交的起始节点。
快慢指针,每个指针循环指向,也就是快指针都对A循环结束后,紧接着对B开始循环,如此往复。而慢指针从B开始。
class Solution {
public:
//返回相交的节点
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* cur_a = headA;
while (cur_a)
{
ListNode* cur_b = headB;
while (cur_b)
{
if (cur_a == cur_b)
{
return cur_a;
}
cur_b = cur_b->next;
}
cur_a = cur_a->next;
}
return nullptr;
}
};
169. 多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [3,2,3]
输出: 3
示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2
hash。定义个字典,key存num,value存出现的次数。如果出现的次数大于n/2,就返回该key。
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map <int,int> hash;
for(int n:nums)
if(++ hash[n] > nums.size()/2) return n;
return -1;
}
};
198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
示例 1:
输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。
简单的动态规划。
- 如果选择偷k号房,那么就不能偷k-1号房。就等于k-2及以前的最大值 + 本次偷的价值。
- 如果不偷,就是k-1及以前的价值。
每次保存当前最优值就好。
class Solution {
public:
int rob(vector<int>& nums) {
vector<int> dp;
int len = nums.size();
if(len==0) return 0;
else if (len==1) return nums[0];
else {
dp.push_back(nums[0]);
dp.push_back(max(nums[0],nums[1]));
for (int i = 2;i<len;i++){
dp.push_back( max( dp[i-2] + nums[i],dp[i-1] ) );
}
return dp[len-1];
}
}
};
206. 反转链表
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
经典题目了。
迭代。定义两个指针,cur指向当前的值(初始化为NULL),pre(我给它一个命名,叫冲锋指针)指向下一个值。不断反向就好了。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *cur = NULL, *pre = head;
while(pre != NULL){
ListNode* t = pre->next;
pre->next = cur;
cur = pre;
pre = t;
}
return cur;
}
};
226. 翻转二叉树
翻转一棵二叉树。
示例:
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
树的题目基本都是递归完成的了。
递归的思想其实就是把零散的步骤看成一个整体步骤。
对于一棵树来说,只需要交换root的左子树和右子树。(对于子树来说也是这样)
所以算法步骤也很简单
- 如果树为NULL,则返回NULL。不需要操作它的子树了。
- 取左子树。
- 取右子树,
- 交换。
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==NULL) return NULL;
TreeNode* left = invertTree(root->left);
TreeNode* right = invertTree(root->right);
root->right = left;
root->left = right;
return root;
}
};
234. 回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false
示例 2:
输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
最粗暴的想法就是存在数组里面,再遍历看看是不是。
但是如果要满足O(1)复杂度,需要
- 用快慢指针,快指针走两步,慢指针走一步,找到链表的中点。
- 翻转链表后半段。
- 通过两个指针来判断是否是回文链表。
我觉得没有太大的意义。就不贴代码了。
283. 移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
说明:
必须在原数组上操作,不能拷贝额外的数组。
尽量减少操作次数。
我的第一思路是冒泡排序的思路,O(n2)。
也可以遇到0就删掉,最后补充。
但作为算法,
- 定义一个count计算非零量。
- nums[count++] = nums[i](当前值),就是往前赋值。
- 最后末尾赋值为0
路径总和 III - 有点难
给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
10
/ \
5 -3
/ \ \
3 2 11
/ \ \
3 -2 1
返回 3。和等于 8 的路径有:
1. 5 -> 3
2. 5 -> 2 -> 1
3. -3 -> 11
这个还是没想明白。先留坑。
448. 找到所有数组中消失的数字
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[5,6]
对出现的值,对其值表示的index上的值加一个offset代表这个值出现过。
再一个遍历看看哪个值没有超过这个offset,就知道缺谁了。
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
vector<int> res;
if(nums.empty()) return nums;
for(int i=0;i<nums.size();i++)
{
int index=(nums[i]-1)%nums.size();
nums[index]+=nums.size();
}
for(int i=0;i<nums.size();i++)
{
if(nums[i]<=nums.size())
res.push_back(i+1);
}
return res;
}
};
543. 二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :
给定二叉树返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
1
/ \
2 3
/ \
4 5
经典树的遍历了。
-
正常计算深度。max( 左子树深度,右子树深度) +1
-
定义一个类变量maxD,计算所有遍历里面最大的直径,即 maxD = max(maxD, 左子树深度 + 右子树深度 )
我用了一个更复杂的传多个值的方式实现,传深度和子节点最大的直径,贴一下吧。
class Solution {
public:
int diameterOfBinaryTree(TreeNode* root) {
if(root ==NULL)return 0;
else{
auto a = helper(root->left);
auto b = helper(root->right);
int diameter = max(max( a[1],b[1]),a[0] + b[0] );
return diameter;
}
}
vector<int> helper(TreeNode* root){
if(root ==NULL)return {0,0};
else{
auto a = helper(root->left);
auto b = helper(root->right);
int depth = max(a[0],b[0]) + 1 ;
int diameter = max(max( a[1],b[1]),a[0] + b[0] );
return {depth,diameter};
}
}
};
581. 最短无序连续子数组
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
示例 1:
输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
说明 :
输入的数组长度范围在 [1, 10,000]。
输入的数组可能包含重复元素 ,所以升序的意思是<=。
先排序是最快的做法。
但如果最好不排序,那寻找左右两边的privot值。
要保证整个数组是升序的,就不能出现峰值。
- 左privot:最早出现的(从左到右)同时大于两边值的index
- 右privot:最早出现的(从右到左)同时小于两边值的index
统计两个privot之间有多少个元素就可以了。
617. 合并二叉树
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
遇到树一般都是递归。
-
如果t1或t2为空,就返回NULL。
-
如果都不为空,就将t2的值加到了t1
-
如果t1左节点为空,就t1->left指向t2->left。
-
如果t1右节点为空,就t1->right指向t2->right。
class Solution {
public:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
if(t1==NULL) return t2;
else if (t2==NULL) return t1;
else return help(t1,t2);
}
TreeNode* help(TreeNode* t1, TreeNode* t2){
//存在为空
if(t1 == NULL || t2==NULL) return NULL;
//都不为空
if( t1 != NULL && t2 !=NULL )
t1->val += t2->val;
if ( help(t1->left,t2->left) ==NULL){
if( t1->left == NULL )
t1->left = t2->left;
}
if ( help(t1->right,t2->right) ==NULL){
if( t1->right == NULL )
t1->right = t2->right;
}
return t1;
}
};
看到解答里面有更清晰的思路,有那个合并多个链表的感觉。
值得学习一下。
class Solution {
public:
TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
if(t1==nullptr && t2==nullptr) return nullptr;
if(t1==nullptr) return t2;
if(t2==nullptr) return t1;
else {
t1->val += t2->val;
t1->left = mergeTrees(t1->left, t2->left);
t1->right = mergeTrees(t1->right, t2->right);
return t1;
}
}
};
后言
其实top100liked的Medium也刷得差不多了。
只是现在的工作重心还不在刷题这里,所以暂且不更新。
欢迎留言以及联系。