leecode个人心得
初入leecode记录自己在刷题过程中遇到的题目
(以下题目中的序号是leecode题目中的序号)
67. 二进制求和
给你两个二进制字符串,返回它们的和(用二进制表示)。
输入为 非空 字符串且只包含数字 1 和 0。
示例 1:
输入: a = “11”, b = “1”
输出: “100”
示例 2:
输入: a = “1010”, b = “1011”
输出: “10101”
提示:
每个字符串仅由字符 ‘0’ 或 ‘1’ 组成。
1 <= a.length, b.length <= 10^4
字符串如果不是 “0” ,就都不含前导零。
解题思路:
参考
二进制求和,满二进一
首先让两个字符串等长,若不等长,在短的字符串前补零,否则之后的操作会超出索引。
然后从后到前遍历所有的位数,同位相加,这里有一个点,用的是字符相加,利用 ASCII 码,字符在内部都用数字表示,我们不需要知道具体数值,但可知 ‘0’-‘0’ = 0, ‘0’+1=‘1’,以此类推 。字符的加减,大小比较,实际上都是内部数字的加减,大小比较
判断相加后的字符,若大于等于字符 ‘2’‘2’,下一位需要进一
第 00 位数的相加在这里是单独处理的,因为它可能涉及到字符的插入(即是否需要在最前面加一位数 ‘1’‘1’
class Solution {
public:
string addBinary(string a, string b) {
int num1=a.size();
int num2=b.size();
while(num1<num2)//如果a的长度小于b
{
a='0'+a;
num1++;
}
while(num1>num2)//如果b的长度小于a
{
b='0'+b;
num2++;
}
for(int i=a.size()-1;i>0;i--)
{
a[i]=a[i]-'0'+b[i];
if(a[i]>='2')
{
a[i]=(a[i]-'0')%2+'0';
a[i-1]=a[i-1]+1;
}
}
a[0]=a[0]-'0'+b[0];
if(a[0]>='2')
{
a[0]=(a[0]-'0')%2+'0';
a='1'+a;
}
return a;
}
};
- 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
解题思路:
第一种:官方解题思路----一次遍历
我们来假设自己来购买股票。随着时间的推移,每天我们都可以选择出售股票与否。那么,假设在第 i 天,如果我们要在今天卖股票,那么我们能赚多少钱呢?
显然,如果我们真的在买卖股票,我们肯定会想:如果我是在历史最低点买的股票就好了!太好了,在题目中,我们只要用一个变量记录一个历史最低价格 minprice,我们就可以假设自己的股票是在那天买的。那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minprice。
因此,我们只需要遍历价格数组一遍,记录历史最低点,然后在每一天考虑这么一个问题:如果我是在历史最低点买进的,那么我今天卖出能赚多少钱?当考虑完所有天数之时,我们就得到了最好的答案。
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;
}
};
结果
第二种—动态规划
- 动态规划做题步骤
明确 dp(i)dp(i) 应该表示什么(二维情况:dp(i)(j)dp(i)(j));
根据 dp(i)dp(i) 和 dp(i-1)dp(i−1) 的关系得出状态转移方程;
确定初始条件,如 dp(0)dp(0)。
2. 本题思路
其实方法一的思路不是凭空想象的,而是由动态规划的思想演变而来。这里介绍一维动态规划思想。
dp[i]dp[i] 表示前 ii 天的最大利润,因为我们始终要使利润最大化,则:
dp[i] = max(dp[i-1], prices[i]-minprice)
dp[i]=max(dp[i−1],prices[i]−minprice)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n == 0) return 0; // 边界条件
int minprice = prices[0];
vector<int> dp (n, 0);
for (int i = 1; i < n; i++){
minprice = min(minprice, prices[i]);
dp[i] = max(dp[i - 1], prices[i] - minprice);
}
return dp[n - 1];
}
};
个人认为第二种方法比第一种更加麻烦一点
第三种–两者综合起来
其实基本的思想不变就是在代码上优化了一下。
70. 爬楼梯-简单
方法一:动态规划
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例 2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
3. 1 阶 + 1 阶 + 1 阶
4. 1 阶 + 2 阶
5. 2 阶 + 1 阶
本题使用动态规划的思想,将大问题化解为小问题,当走到第n阶台阶时,前一步只能分为两种情况,一个是n-1阶走一步,另一个是n-2阶走两步,用 f(x) 表示爬到第 x 级台阶的方案数,即f(n)=f(n-1)+f(n-2),以此类推,在边界处即f(0)=1,从第 0 级爬到第 0 级我们可以看作只有一种方案,即f(0)=1;从第 0 级到第 1 级也只有一种方案,即爬一级,f(1) = 1。这两个作为边界条件就可以继续向后推导出第 nn 级的正确结果。我们不妨写几项来验证一下,根据转移方程得到 f(2) = 2,f(3) = 3,f(4) = 5,……,我们把这些情况都枚举出来,发现计算的结果是正确的。用「滚动数组思想」把空间复杂度优化成 O(1)。代码和测试结果如下:
方法二:斐波那契数列
其实方法一就有斐波那契数列的影子,直接使用斐波那契数列数列的公式:
543. 二叉树的直径
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :给定二叉树
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
注意:两结点之间的路径长度是以它们之间边的数目表示。
方法一:递归思想
类似于树的深度遍历,一条路径的长度为该路径经过的节点数减一,所以求直径(即求路径长度的最大值)等效于求路径经过节点数的最大值减一。所以直径是左右子树的高度相加,一直递归到根结点。如下:1的左子树高度为4,右子树高度为1,相加为5是直径,但在写代码时注意计算本结点高度返回值要+1。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int maxd = 0;
int depth(TreeNode* node)
{
if(node == NULL)
return 0;
int left = depth(node->left);
int right = depth(node->right);
maxd = max ( maxd , left + right);//最大直径是左右子树的高度相加
return max(left,right) + 1;//返回树的高度
}
int diameterOfBinaryTree(TreeNode* root) {
depth(root);
return maxd;
}
};
448. 找到所有数组中消失的数字
给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。
找到所有在 [1, n] 范围之间没有出现在数组中的数字。
您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。
示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[5,6]
方法:原地修改,设计特殊标记的方法
在题目中数组中的值和数组下标范围一致都是[1,n],所以根据这个设置规律,第一种:遍历输入数组的每个元素一次。我们将把 |nums[i]|-1 索引位置的元素标记为负数。即 nums[|nums[i] |- 1] =nums[∣nums[i]∣−1]×−1 。然后遍历数组,若当前数组元素 nums[i] 为负数,说明我们在数组中存在数字 i+1。这样把数组中的值和数组下标联系起来。代码如下:
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
if (nums.empty())
return nums;
vector<int> result;
for(int i=0; i < nums.size();i++)
{
if (nums[abs(nums[i]) - 1] > 0)//注意,自己第一次编写的时候没有考虑重复情况,若两次*-1则为原数
nums[abs(nums[i])-1] *= -1;
}
for(int i=0; i < nums.size() ; i++)
{
if(nums[i] > 0 )
result.push_back(i + 1);
}
return result;
}
};
第二种:遍历数组,查找下标为(nums[i]-1)%nums.size()的数组元素,将其值加nums.size(),最后再次遍历数组,如果存在有值小于或等于nums.size()的元素,则其i+1不存在。代码:
class Solution {
public:
vector<int> findDisappearedNumbers(vector<int>& nums) {
if (nums.empty())
return nums;
vector<int> result;
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())
result.push_back(i+1);
}
return result;
}
};
461. 汉明距离
两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。
给出两个整数 x 和 y,计算它们之间的汉明距离。
注意:
0 ≤ x, y < 231.
示例:
输入: x = 1, y = 4
输出: 2
解释:
1 (0 0 0 1)
4 (0 1 0 0)
解题思路:本题关键时两个数字对应二进制位不同的位置的数目,则异或运算十分方便,将整数二进制化再进行异或求出1的个数。
第一种:bitset<32>定义32位的位运算,然后用count记录1的数量。这种方法自己对于该库函数不是很熟悉,推荐第二种。
class Solution {
public:
int hammingDistance(int x, int y) {
return bitset<32> (x ^ y).count();
}
};
第二种:将x,y不断%2,求出末位的二进制数字分别保存在temp1和temp2中,再temp1^temp2,将结果添加在result中,result来计数。
class Solution {
public:
int hammingDistance(int x, int y) {
//return bitset<32> (x ^ y).count();
int temp1=0,temp2=0,result=0;
while(x|y)//检查x与y是否结束
{
temp1 = x%2;
temp2 = y%2;
result += ( temp1 ^ temp2 );
x = x/2;//更新x
y = y/2;//更新y
}
return result;
}
};
160. 相交链表
方法一: 暴力法
对链表A中的每一个结点 temp1,遍历链表B 检查其中是否存在结点temp1。
时间复杂度O(m * n) 空间复杂度O(1)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA == NULL | headB == NULL)
return NULL;
ListNode *temp1 = headA;
ListNode *temp2;
while (temp1)
{
temp2 = headB;
while (temp2)
{
if (temp1 == temp2)
return temp1;
temp2 = temp2 -> next;
}
temp1 = temp1->next;
}
return temp1;
};
};
方法三:双指针法
参考题解:一种比较巧妙的方式是,分别为链表A和链表B设置指针A和指针B,然后开始遍历链表,如果遍历完当前链表,则将指针指向另外一个链表的头部继续遍历,直至两个指针相遇。
最终两个指针分别走过的路径为:
指针A :a+c+b
指针B :b+c+a
明显 a+c+b = b+c+a,因而如果两个链表相交,则指针A和指针B必定在相交结点相遇。
若无焦点,则二者最后都是null
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA == NULL | headB == NULL)
return NULL;
ListNode *temp1 = headA;
ListNode *temp2 = headB;
while (temp1 != temp2)
{
if(temp1 != NULL)
temp1 = temp1->next;
else
temp1 = headB;
if(temp2 != NULL)
temp2 = temp2->next;
else
temp2 = headA;
}
return temp1;
};
};
- 翻转二叉树
翻转一棵二叉树。
解题思路:递归思想,从根部遍历到叶子,再从叶子结点开始反转,从下到上。我们从根节点开始,递归地对树进行遍历,并从叶子结点先开始翻转。如果当前遍历到的节点 root 的左右两棵子树都已经翻转,那么我们只需要交换两棵子树的位置,即可完成以 root 为根节点的整棵子树的翻转。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root == nullptr)
return nullptr;
TreeNode* left = invertTree(root->left);
TreeNode* right = invertTree(root->right);
root->right = left;
root->left = right;
return root;
}
};
141. 环形链表
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false 。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
**解题思路:**设置一对快慢指针,慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
时间复杂度:O(N),其中 N 是链表中的节点数。
当链表中不存在环时,快指针将先于慢指针到达链表尾部,链表中每个节点至多被访问两次。
当链表中存在环时,每一轮移动后,快慢指针的距离将减小一。而初始距离为环的长度,因此至多移动 N 轮。
空间复杂度:O(1)。我们只使用了两个指针的额外空间。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if (head == nullptr || head->next == nullptr)
return false;
ListNode* slow = head;
ListNode* fast = head->next;
while(slow != fast)
{
if(slow == nullptr || fast == nullptr || fast -> next == NULL)
return false;
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
169. 多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:[3,2,3]
输出:3
示例 2:
输入:[2,2,1,1,1,2,2]
输出:2
**解题思路:**由题可知,题目中一定存在一个出现次数大于 ⌊ n/2 ⌋ 的元素,第一种方法是使用hash表方法,使用数据结构unordered_map元素,来进行计数。
class Solution {
public:
int majorityElement(vector<int>& nums)
{
unordered_map<int, int> count;
int majority = 0, most = 0;
for (int i = 0; i < nums.size(); i++)
{
++count[nums[i]];
if (count[nums[i]] > most)
{
majority = nums[i];
most = count[nums[i]];
}
}
return majority;
}
};
不过在题解中,自己看到for的另一种循环方式for(int num:nums)
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map<int, int> counts;
int majority = 0, cnt = 0;
for (int num: nums) {
++counts[num];
if (counts[num] > cnt) {
majority = num;
cnt = counts[num];
}
}
return majority;
}
};
第二种思路是当出现次数大于⌊ n/2 ⌋ 时,则该数一定是众数,将原数组排序后再进行访问nums.size()/2的位置。
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(),nums.end());
return nums[nums.size() / 2];
}
};
- 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
方法一:暴力破解
思路:很简单使用两个for语句,嵌套遍历来访问,当相加为target的值的时候返回下标。
时间复杂度:O(N^2)其中 N 是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。
空间复杂度:O(1)O(1)。