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;
}
};