1.两数之和
描述:给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。
暴力循环
没啥好说的,最容易想到的逻辑,时间复杂度O(n2)
双指针
排序后,利用双指针向中间逼近,知道找到目标值,时间复杂度O(nlogn)
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int left = 0, right = numbers.size() - 1;
while (left < right)
{
if (numbers[left] + numbers[right] < target)
left++;
else if (numbers[left] + numbers[right] > target)
right--;
else
return vector<int>{left + 1, right + 1};
}
return {};
}
};
一遍字典
对于当前元素x,与之匹配的另一数字为target-x,我们每遍历一个x,都将target-x作为key添加到字典中,其值为下标,在之后的遍历过程中,一旦发现当前x已存在字典中,说明之前已经找到匹配的元素,然后字典返回其下标。时间复杂度O(n)
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int> ret = { -1, -1 };
map<int, int> m;
for (int i = 0; i < nums.size(); i++)
{
int pair = target - nums[i];
int x = m[pair];
if (m[pair])
{
ret[0] = m[pair] - 1; ret[1] = i;
return ret;
}
m[nums[i]] = i + 1;
}
return ret;
}
};
2.两数相加
描述:给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
这题熟悉链表的话还是很容易实现吧,稍微得注意下进位问题
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int car = 0;
ListNode* res = NULL;
ListNode* p0 = res;
ListNode* p1 = l1;
ListNode* p2 = l2;
while (p1 != NULL && p2 != NULL)
{
if (!res)
{
res = new ListNode((p1->val + p2->val + car) % 10);
p0 = res;
}
else
{
p0->next = new ListNode((p1->val + p2->val + car) % 10);
p0 = p0->next;
}
if (p1->val + p2->val + car >= 10)
car = 1;
else
car = 0;
p1 = p1->next;
p2 = p2->next;
}
while (p1)
{
p0->next = new ListNode((p1->val + car) % 10);
if (p1->val + car >= 10)
car = 1;
else
car = 0;
p1 = p1->next;
p0 = p0->next;
}
while (p2)
{
p0->next = new ListNode((p2->val + car) % 10);
if (p2->val + car >= 10)
car = 1;
else
car = 0;
p2 = p2->next;
p0 = p0->next;
}
if (car == 1)
p0->next = new ListNode(1);
return res;
}
};
3.无重复字符的最长子串
描述:给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
解决该问题可以使用滑动窗口模型。我们维护一个滑动窗口,并限制其中的字符集不可重复,每次滑动窗口进行扩张,若出现重复则收缩窗口。在具体实现中,我们可以用两个下标来作为滑动窗口的边界,以一个字典来维护字符集。然后保存出现过的最大长度。
如何收缩窗口有两种策略,这与我们的字典有关。
若字典的值为bool型,即我们只关心该字符是否存在,则左边界每次向右收缩一个单位,每次收缩都去除掉某个字符,直到右边界扩张进的新字符没有出现过为止。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
map<int, int> m;
int left = 0, right = -1;
int res = 0;
for (int i = 0; i < s.length(); i++)
{
if (m[s[i]])
{
m[s[left]] = false;
left++;
i--;
continue;
}
right++;
m[s[i]] = true;
res = max(res, right - left + 1);
}
return res;
}
};
若字典的值为某个字符出现的下标,则我们每次收缩都收缩到该下标+1处。若这个下标比左边界小,则收缩到左边界+1
class Solution {
public:
int lengthOfLongestSubstring(string s) {
map<int, int> m;
int left = 0, right = 0;
int res = 0;
for (int i = 0; i < s.length(); i++)
{
if (m[s[i]] && m[s[i]] > left)
left = max(left + 1, m[s[i]]);
right = i;
m[s[i]] = i + 1;
res = max(res, right - left + 1);
}
return res;
}
};
9.回文数
描述:判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
正常思路:整数转为字符串,逆序字符串与原字符串比较,若相等则是回文数
本题要求我们不转换为字符串,即只能获取整数的各个位的数来进行操作。我们可以通过迭代的方式反转这个整数,然后将其与原整数比较即可,不过这里可能出现数字反转后导致的整数溢出问题,所以我们可以仅反转后半部分,再将其与剩下的前半部分比较即可
class Solution {
public:
bool isPalindrome(int x) {
if (x < 0)
return false;
else if (x == 0)
return true;
else if (x % 10 == 0)
return false;
int num = 0;
while (x > num)
{
num = num * 10 + x % 10;
x /= 10;
}
return x == num || x == num / 10;
}
};
当然,也可以依次比较最高位和最低位直至比较结束。
11.盛最多水的容器
题目描述:
描述得挺抽象,看图就比较容易理解。也就是说,要找到最大面积,而面积是由两边中较短的高以及长度来决定的。自然最容易想到的是遍历所有组合,不过时间复杂度为O(n2)。若我们先从最大长度开始遍历,则会发现,有些组合是没有必要的,即每次收缩长度时是可以判断方向的,由于要找的是最大面积,而在长度一定时,面积是由较短高度来决定的,最大面积不会同时出现在两边,所以我们应该从高度更短的那一边开始收缩,这样,本题就转换为了双指针的模型,可用O(nlogn)的时间复杂度解决
class Solution {
public:
int maxArea(vector<int>& height) {
int max_area = 0;
int left = 0, right = height.size() - 1;
while (left < right)
{
max_area = max(max_area, (right - left) * min(height[left], height[right]));
if (height[left] < height[right])
left++;
else
right--;
}
return max_area;
}
};
15.三数之和
描述:给定一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
第一题的升级版,这里字典的方法不太好用了,而双指针依旧可以使用,不过由于是3个数,所以时间复杂度为O(n2),我们可以进行一些局部的优化
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
if (nums.empty() || nums.front() > 0 || nums.back() < 0)
return {};
for (int i = 0; i < nums.size(); i++)
{
int fix = nums[i];
if (fix > 0) //最左值为负无解
break;
if (i > 0 && fix == nums[i-1]) //fix重复优化
continue;
int left = i + 1, right = nums.size()-1;
while (left < right)
{
if (nums[left] + nums[right] == -fix)
{
if (left == i+1 || right == nums.size()-1)
{
res.push_back(vector<int>{fix, nums[left], nums[right]});
left++, right--;
}
else if (nums[left] == nums[left-1]) //双指针左值重复优化
left++;
else if (nums[right] == nums[right+1]) //双指针右值重复优化
right--;
else
{
res.push_back(vector<int>{fix, nums[left], nums[right]});
left++, right--;
}
}
else if (nums[left] + nums[right] < -fix)
left++;
else
right--;
}
}
return res;
}
};
20.有效的括号
描述:给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效。
栈的使用,没啥好说的
class Solution {
public:
bool isPair(vector<char>& s, char c) {
if (s.size() == 0)
return false;
if (s.back() == c)
{
s.pop_back();
return true;
}
else
return false;
}
bool isValid(string s) {
vector<char> stack;
for (int i = 0; i < s.length(); i++)
{
if (s[i] == '(' || s[i] == '[' || s[i] == '{')
stack.push_back(s[i]);
else
switch (s[i])
{
case ')':
if (!isPair(stack, '('))
return false;
break;
case ']':
if (!isPair(stack, '['))
return false;
break;
case '}':
if (!isPair(stack, '{'))
return false;
break;
default:
break;
}
}
if (stack.size() == 0)
return true;
else
return false;
}
};
26.删除排序数组中重复的元素
描述:给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
没有重复元素很容易联想到集合,即我们直接将该数组插入一个set,然后再复制回去即可,不过这样就利用了额外空间,且本质上完全是依赖STL完成。所以在这里,我们依旧可以使用双指针,一个指向下一个缓冲区的下标,另一个一直前进。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int i = 0;
if (nums.empty())
return 0;
for (int j = 1; j < nums.size(); j++)
{
if (nums[i] != nums[j])
{
nums[++i] = nums[j];
}
}
return i + 1;
}
};
88.合并两个有序数组
描述:给定两个有序整数数组 nums1 和 nums2,将 nums2 合并到 nums1 中,使得 num1 成为一个有序数组。
用一个额外的临时空间存放最终结果,每次取更合适的值,然后放回nums1中
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
vector<int> temp;
int i = 0, j = 0;
if (!m || !n)
{
if (!m)
nums1 = nums2;
return;
}
while (true)
{
if (nums1[i] < nums2[j])
temp.push_back(nums1[i++]);
else
temp.push_back(nums2[j++]);
if (i > m - 1)
{
temp.insert(temp.end(), nums2.begin()+j, nums2.begin()+n);
break;
}
if (j > n - 1)
{
temp.insert(temp.end(), nums1.begin()+i, nums1.begin()+m);
break;
}
}
for (int i = 0; i < temp.size(); i++)
nums1[i] = temp[i];
}
};
104.二叉树最大深度
描述:给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
其实就是遍历一下吧...
/**
* 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 maxDepth(TreeNode* root) {
if (root == NULL)
return 0;
else
{
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
}
};
121.买股票的最佳时机
描述:给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。
要求得到最大利润,而利润是根据 当前卖出价格-最初买入价格 得到的,我们可以维护两个变量,一个是之前的最低价格,一个则是最大利润,它们都在遍历过程中进行更新,最终得到的最大利润就是所求
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.empty() || prices.size() == 1)
return 0;
int min_price = prices[0];
int max_profit = 0;
for (int i = 1; i < prices.size(); i++)
{
int profit = prices[i] - min_price;
if (profit > max_profit)
max_profit = profit;
if (min_price > prices[i])
min_price = prices[i];
}
if (max_profit < 0)
max_profit = 0;
return max_profit;
}
};
122.买股票的最佳时机2
与上题不同的是,本题可以无限次的买卖,但依旧是求最大利润。这里其实只要考虑到了一个点就会变得十分容易,即只要每次买卖是获利的,那么累加起来一定就是最大利润
class Solution {
public:
int maxProfit(vector<int>& prices) {
int max_profit = 0;
if (prices.empty() || prices.size() == 1)
return 0;
for (int i = 1; i < prices.size(); i++)
{
int profit = prices[i] - prices[i-1];
if (profit > 0)
max_profit += profit;
}
return max_profit;
}
};
136.只出现一次的数字
描述:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
正常人都会想到用字典,但这里题目希望不使用额外空间,于是就有一种很骚的办法
class Solution {
public:
int singleNumber(vector<int>& nums) {
int res = 0;
if (nums.empty())
return 0;
for (int i = 0; i < nums.size(); i++)
res ^= nums[i];
return res;
}
};
155.最小栈
额,就是写个栈,不过要求能够直接返回最小值,那么这个最小值应该作为该栈的一个成员字段存在,而非每次去搜索,需要注意下边界问题。虽然可以直接用数组来实现,不过这样就固定了最大容积,所以就用了vector
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
}
void push(int x) {
if (x < min)
min = x;
stack.push_back(x);
}
void pop() {
if (this->top() == min)
{
stack.pop_back();
min = *min_element(stack.begin(), stack.end());
}
else
stack.pop_back();
if (stack.empty())
min = (int)((unsigned)(~0) >> 1);
}
int top() {
return stack.back();
}
int getMin() {
return min;
}
private:
vector<int> stack;
int min = (int)((unsigned)(~0) >> 1);
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
169.求众数
描述:给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋
的元素。
用一个字典保存一个数出现的次数,然后找到次数大于n/2的元素
class Solution {
public:
int majorityElement(vector<int>& nums) {
map<int, int> hash;
for (int i = 0; i < nums.size(); i++)
{
if (!hash[nums[i]])
hash[nums[i]] = 1;
else
hash[nums[i]] += 1;
if (hash[nums[i]] > nums.size() / 2)
return nums[i];
}
return 0;
}
};
由于众数在数组中出现次数大于n/2,则排序后中间的数一定是众数,所以也可以排序然后找中位数。
172.阶层后的0
描述:给定一个整数 n,返回 n! 结果尾数中零的数量
很容易想到先算出该数,然后再迭代求各个位数,但阶层的数值变迁很大,所以在测试的时候很容易就超过bit可表示范围。可以通过找规律,或者说数论的方法来解决该问题。即寻找其前n项积中有多少个5,需要找一找规律
class Solution {
public:
int trailingZeroes(int n) {
if (n < 5)
return 0;
return trailingZeroes(n / 5) + n / 5;
}
};
190.颠倒二进制位
描述:颠倒给定的 32 位无符号整数的二进制位。
位运算吧,符合逻辑就行
class Solution {
public:
uint32_t reverseBits(uint32_t n) {
int res = 0;
for (int i = 0; i < 32; i++)
{
res |= (n & 1) << (31 - i);
n >>= 1;
}
return res;
}
};
191.位1的个数
描述:编写一个函数,输入是一个无符号整数,返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为汉明重量)。
递归或迭代都很好做
class Solution {
public:
int hammingWeight(uint32_t n) {
if (n == 0)
return 0;
return hammingWeight(n / 2) + n % 2;
}
};
198.打家劫舍
描述:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
203.删除链表元素
描述:删除链表中等于给定值 val 的所有节点。
注意边界问题
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if (!head)
return head;
while (head->val == val)
{
ListNode* temp = head;
head = head->next;
delete(temp);
if (!head)
return head;
}
ListNode* node = head;
while (node->next)
{
if (node->next->val == val)
{
ListNode* temp = node->next;
node->next = temp->next;
delete(temp);
continue;
}
node = node->next;
}
return head;
}
};
206.反转链表
描述:反转一个单链表。
要反转一个链表,势必会改变结点的next指针,需要维护当前结点与下一结点的指针,这种方式很容易实现,当然,如下的递归也是很骚了,即我们反转一个链表,则head->next开始的链表也要反转,最终归结为一系列子问题
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !(head->next))
return head;
ListNode* re = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return re;
}
};
219.存在重复元素
描述:给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的绝对值最大为 k。
字典保存下标
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
if (nums.empty())
return false;
map<int, int> hash;
for (int i = 0; i < nums.size(); i++)
{
if (!hash[nums[i]])
{
if (nums[0] == nums[i] && i != 0 && k >= i)
return true;
hash[nums[i]] = i;
}
else if (k >= i - hash[nums[i]])
return true;
}
return false;
}
};
226.翻转二叉树
描述:翻转一棵二叉树
嘛,也就是基本递归了
/**
* 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:
TreeNode* invertTree(TreeNode* root) {
if (!root)
return NULL;
root->left = invertTree(root->left);
root->right = invertTree(root->right);
TreeNode* temp = root->left;
root->left = root->right;
root->right = temp;
return root;
}
};
263.丑数
描述:编写一个程序判断给定的数是否为丑数。丑数就是只包含质因数 2, 3, 5
的正整数。
一直判断是否能整除2,3,5即可
class Solution {
public:
bool isUgly(int num) {
if (num == 0) return false;
if (num == 1) return true;
if (num % 2 == 0) return isUgly(num / 2);
if (num % 3 == 0) return isUgly(num / 3);
if (num % 5 == 0) return isUgly(num / 5);
return false;
}
};
283.移动0
描述:给定一个数组 nums
,编写一个函数将所有 0
移动到数组的末尾,同时保持非零元素的相对顺序。
双指针,与删除重复元素类似
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int index = 0;
for (int i = 0; i < nums.size(); i++)
{
if (nums[i] != 0)
nums[index++] = nums[i];
}
for (int i = index; i < nums.size(); i++)
nums[i] = 0;
}
};
349.两个数组的交集
描述:给定两个数组,编写一个函数来计算它们的交集
放个字典
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
vector<int> res;
map<int, int> hash;
for (int i = 0; i < nums1.size(); i++)
{
if (!hash[nums1[i]])
hash[nums1[i]] = 1;
}
for (int i = 0; i < nums2.size(); i++)
{
if (hash[nums2[i]])
{
res.push_back(nums2[i]);
hash[nums2[i]] = 0;
}
}
return res;
}
};
371.两整数之和
描述:不使用运算符 +
和 -
,计算两整数 a
、b
之和
逻辑电路中加法器原理,与操作表示进位,异或表示不进位加法,递归实现
class Solution {
public:
int getSum(int a, int b) {
if (a == 0) return b;
if (b == 0) return a;
return getSum(a ^ b, (unsigned)(a & b) << 1);
}
};
575.分糖果
描述:给定一个偶数长度的数组,其中不同的数字代表着不同种类的糖果,每一个数字代表一个糖果。你需要把这些糖果平均分给一个弟弟和一个妹妹。返回妹妹可以获得的最大糖果的种类数
仔细分析这个问题,我们发现会出现两种情况,一是糖果种类大于n/2,二是糖果种类小于n/2。在第一种情况下,妹妹获得的最大糖果数就是其获得的糖果数量;在第二种情况下,妹妹获得的最大糖果数就是糖果总类数量
class Solution {
public:
int distributeCandies(vector<int>& candies) {
set<int> s;
for (auto it:candies)
s.insert(it);
if (candies.size() / 2 > s.size())
return s.size();
else
return candies.size() / 2;
}
};