LeetCode刷題記錄(四)
劍指 Offer 42. 連續子陣列的最大和(dp)
求連續子陣列的最大和,要最大,即加上後的值變大,所以我們用一個數組dp[]來記錄該連續和,如果加上num[i]變大代表需要這個數,變小代表不需要這個數就不加,流程如下:
class Solution { public: int maxSubArray(vector<int>& nums) { vector<int>dp; dp.resize(nums.size()); dp[0] = nums[0]; int res = nums[0]; for(int i = 1;i<nums.size();i++){ //如果當前字首和加上該結點值變大就加上,否則不加 dp[i] = dp[i-1]+nums[i]>nums[i] ? dp[i-1]+nums[i] : nums[i]; res = res>dp[i] ? res : dp[i];//更新最大值 } return res; } };
上面的程式碼的空間複雜度為O(n),還可以進一步優化
當陣列很大時,使用滾動陣列可以節約相當空間
class Solution { public: int maxSubArray(vector<int>& nums) { int currentMax = nums[0]; int res = nums[0]; for(int i = 1;i<nums.size();i++){ int preMax = currentMax;//將當前的最大值賦給preMax //當前的最大值為 currentMax = currentMax+nums[i]>nums[i] ? currentMax+nums[i] : nums[i]; //判斷是否更新結果最大值 res = res>currentMax ? res : currentMax; } return res; } };
劍指 Offer 50. 第一個只出現一次的字元
解法一:用一個map存對應關係
class Solution { public: vector<map<char,int>>v; char firstUniqChar(string s) { if(s.length()==0) return ' '; map<char,int>m; for(char ch:s){ if(m.find(ch)==m.end()){ //還未加入,則加入 m.insert(make_pair(ch,1)); // v.push_back(m); }else{//在map中已經存在,則記數值++ m[ch]++; } } char res = ' '; for( char c:s ){ if(m[c]==1){ res = c; break; } } return res; } };
高階寫法
class Solution {
public:
char firstUniqChar(string s) {
unordered_map<char, bool> dic;
for(char c : s)//取出每一個字元
//如果c字元未找到,將true賦值給dic[c],如果找到了代表出現不止一次,將false賦值給dic[c]
dic[c] = dic.find(c) == dic.end();
for(char c : s)
if(dic[c]) return c;//找到第一個為true的c返回
return ' ';
}
};
方法二:有序雜湊表
class Solution {
public:
char firstUniqChar(string s) {
vector<char> keys;
unordered_map<char, bool> dic;
for(char c : s) {
if(dic.find(c) == dic.end())
keys.push_back(c);
dic[c] = dic.find(c) == dic.end();
}
for(char c : keys) {
if(dic[c]) return c;
}
return ' ';
}
};
劍指 Offer 52. 兩個連結串列的第一個公共節點
大佬的寫法
思路是這樣的:因為兩個要找到兩個連結串列的第一個公共結點(一定存在),但是兩個連結串列在公共結點之前的結點數目又不一樣,所有讓兩個連結串列分別遍歷自己和對方,則一定會一起走到相同結點。
兩個連結串列長度分別為L1+C、L2+C, C為公共部分的長度, 第一個人走了L1+C步後,回到第二個人起點走L2步;第2個人走了L2+C步後,回到第一個人起點走L1步。 當兩個人走的步數都為L1+L2+C時就走到了公共結點
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode * node1 = headA;
ListNode * node2 = headB;
while(node1 != node2){
//判斷1的路走完沒有,走完就走2的路,同理2
node1 = node1!=NULL ? node1->next : headB;
node2 = node2!=NULL ? node2->next : headA;
}
return node1;
}
};
劍指 Offer 53 - I. 在排序陣列中查詢數字 I
這道題,面試中一般是想考察你的二分查詢
class Solution {
public:
int search(vector<int>& nums, int target) {
int count = 0;
for(int num :nums){
if(num==target){
count++;
}
}
return count;
}
};
二分寫法:
二分的條件是該序列是有序排列的
這裡的二分思想是這樣的,1.去找traget的右邊界,即第一個不為target的值
2.去找target-1的右邊界,即第一個target出現的位置
3.然後將兩個位置下標相減,得到最終結果
class Solution {
public:
int search(vector<int>& nums, int target) {
//return 右邊界-左邊界 就可以得到個數
return bin_search(nums,target) - bin_search(nums,target-1);
}
int bin_search(vector<int>&nums ,int target){
int low =0,high = nums.size()-1;
while(low <= high){
int mid = (low + high)/2;
if(nums[mid]<=target)
{
low = mid+1;
}else{
high = mid - 1;
}
}
return low;
}
};
劍指 Offer 53 - II. 0~n-1中缺失的數字
解法一:無腦解法
class Solution {
public:
int missingNumber(vector<int>& nums) {
int res = 0;
for(int num:nums){
if(num!=res){
break;
}
res ++;
}
return res;
}
};
解法二:二叉查詢
思路:如果下標數等於mid代表左邊的都已經有序了(因為如果不等於代表一定有缺失的值)此時向右邊搜尋,否則代表左邊是無序的向右邊搜尋
class Solution {
public:
int missingNumber(vector<int>& nums) {
int low =0,high = nums.size()-1;
while(low<=high){
int mid = (low+high)/2;
if(mid==nums[mid]){ //左邊未出現缺失
low = mid+1;
}else{
high = mid-1;
}
}
return low;
}
};
劍指 Offer 54. 二叉搜尋樹的第k大節點
如上圖按照右根左的遍歷序列可以直接得到第K大的數
class Solution {
public:
int k,res;
int kthLargest(TreeNode* root, int k) {
this->k = k;
dfs(root);
return res;
}
void dfs(TreeNode *root){
if(root==nullptr) return ; //邊界條件
dfs(root->right); //向右遍歷
if(k==0) return ;
if(--k==0) res = root->val;
dfs(root->left); //向左遍歷
}
};
樹的中序遍歷(左根右)
中序遍歷的結果為從小到大的排序,要找到第K大的數即為倒數第K個即v[count-k]
class Solution {
public:
int count=0,res,k;
vector<int>v;
int kthLargest(TreeNode* root, int k) {
this->k = k;
dfs(root);
return v[count-k];
}
void dfs(TreeNode *root){
// 左根右
if(root==nullptr) return;
if(root->left) dfs(root->left);
v.push_back(root->val);
count++;
if(root->right) dfs(root->right);
}
};
劍指 Offer 65. 不用加減乘除做加法
考察異或,位運算這類的問題要對計算機組成原理有一定的瞭解,其實在底層乘法器和除法器都是依靠加法器和移位來實現的,而加法器的實現靠的是本位和和進位(Cin)來計算的
class Solution {
public:
int add(int a, int b) {
//因為不允許用+號,所以求出異或部分和進位部分依然不能用+ 號,所以只能迴圈到沒有進位為止
while(b!=0)
{
//儲存進位值,下次迴圈用
int c=(unsigned int)(a&b)<<1;//C++中負數不支援左移位,因為結果是不定的
//儲存不進位值,下次迴圈用,
a^=b;
//如果還有進位,再迴圈,如果沒有,則直接輸出沒有進位部分即可。
b=c;
}
return a;
}
};
劍指 Offer II 024. 反轉連結串列
連結串列的原地逆置
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == nullptr) return head;
// 連結串列的原地逆置
ListNode *p,*q = head;
p = head ->next;
head->next = NULL;
while(p){
q = p->next;
p->next = head;
head = p;
p = q;
}
return head;
}
};
面試題 01.02. 判定是否互為字元重排
就是兩個串,判斷B串是否為A串的重新排列,那隻用判斷B串中是否僅含A串的字母,和上面的劍指offer50類似用一個map解決
class Solution {
public:
bool CheckPermutation(string s1, string s2) {
//僅需要判斷是不是A中的字元B中都有就可以了
map<char,int>m;
bool res = true;
for(char each:s1){
if(m.find(each)==m.end()){
m.insert(make_pair(each,1));
// m[each]++;
}else{
m[each]++;
}
}
for(char each:s2){
if(m.find(each)==m.end()||m[each]==0){
return false;
}else{
m[each]--;
}
}
return res;
}
};
劍指 Offer 55 - I. 二叉樹的深度
解法一:
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==NULL) return 0;
int left = maxDepth(root->left); //左遞迴
int right = maxDepth(root->right); //右遞迴
return left>right ? left+1 :right+1; //返回條件,每次向上返回時,left和right各自加一
}
};
解法二:
注意,當用層次遍歷需要記錄層次數時,使用for(int i = q.size();i>0;i--)
class Solution {
int count = 0;
public:
int maxDepth(TreeNode* root) {
if(root == NULL) return 0;
queue<TreeNode*> q; //建立佇列q
q.push(root); //將root推入佇列q
while(!q.empty()){
for(int i = q.size(); i; i--){
auto node = q.front();
q.pop();
if(node->left != NULL) q.push(node->left);
if(node->right != NULL) q.push(node->right);
}
count++;
}
return count;
}
};
劍指 Offer 55 - II. 平衡二叉樹
判斷一顆樹是不是平衡二叉樹,可以用上面的解法一的思路,對每一個結點的左右都判斷深度差,當所有結點左右子樹深度差小於2時代表該樹是一顆平衡二叉樹
解法一:
class Solution {
public:
bool isBalanced(TreeNode* root) {
return dfs(root)>0 ? true :false;
}
int dfs(TreeNode *root){
if(root==NULL) return true;
int left = dfs(root->left);//向左遍歷
if(left==-1) return -1;
int right = dfs(root->right);//向右遍歷
if(right==-1) return -1;
return abs(left-right)<2 ? max(left,right)+1 :-1;//判斷左右之差是否小於2
}
};
解法二:
/* 先序遍歷+判斷深度 (從左到右,自頂向下。因為對每個節點 都進行了dfs,所以會產生大量重複計算,時間複雜度較高)*/
class Solution {
private:
int dfs(TreeNode* root);
public:
bool isBalanced(TreeNode* root) {
if (nullptr == root) return true;
int left_deep = dfs(root->left); // 以該root為根節點的樹 的左子樹深度
int right_deep = dfs(root->right); // 以該root為根節點的樹 的右子樹深度
if (std::abs(left_deep - right_deep) > 1) return false; // 0<=左右子樹的深度差<=1 才滿足條件,否則返回false
return isBalanced(root->left) && isBalanced(root->right); // 繼續遍歷該樹的其它節點,並檢查 以每個節點為root的子樹 是否為平衡二叉樹
}
};
int Solution::dfs(TreeNode* root) { // 以該root為根節點的樹 的深度
if (nullptr == root) return 0;
return std::max(dfs(root->left), dfs(root->right)) + 1;
}
劍指 Offer 56 - I. 陣列中數字出現的次數
僅兩個數字僅出現一次,其餘都出現兩次,思路如下注釋
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int x = 0, y = 0, n = 0, m = 1;
// 以{4,1,4,6,3,3}為例
for(int num : nums) // 1. 遍歷異或
n ^= num; //最終為1^6 = 0111= 7
while((n & m) == 0) // 2. 迴圈左移,計算 m
m <<= 1; //獲取首位1 m=0001
// 再用這個首位1為分界線將其分為兩組
// 為何?1 --> 0001
// 6 --> 0110
// 即找到他們有差別的那一位數字
// 那麼我們再在nums中取數,1&m=0001一定不為0 6&m=0000一定為0
// 其他數字都出現兩次,一定抵消,則結果一定為1和6
for(int num : nums) { // 3. 遍歷 nums 分組
if(num & m) x ^= num; // 4. 當 num & m != 0
else y ^= num; // 4. 當 num & m == 0
}
return vector<int> {x, y}; // 5. 返回出現一次的數字
}
};
劍指 Offer 56 - II. 陣列中數字出現的次數 II
沒有複雜度要求,可以考慮計數這樣是雙O(n)
解法一:
利用map對映,該數字出現就計數
class Solution {
public:
int singleNumber(vector<int>& nums) {
if(nums.size()==0) return 0;
// 法一:map對映
map<int,int>m;
// 1.是數字 2.是count數量
for(int num:nums){
if(m.find(num)!=m.end()) m[num]++;
else{
m.insert(make_pair(num,1));
}
}
int res;
for(pair<int,int>a:m){
if(a.second!=3){
res = a.first;
break;
}
}
return res;
}
};
解法二:將每位上的二進位制位數都統計一遍,然後再對3取餘,則剩下的為所求的二進位制位數
原理就是其餘數字都出現3次,則轉換為2進位制後每一位都會出現3次,取餘後相當於消去了出現3次的數,則剩下的為所求
// 位運算 + 遍歷統計
class Solution {
public:
int singleNumber(vector<int>& nums) {
vector<int> vec(32); // 記錄所有數字的各二進位制位的 1的出現次數
for (int i = 0; i < nums.size(); ++i) {
unsigned int m = 1;
for (int j = 0; j < 32; ++j) {
if ((m & nums[i]) != 0) ++vec[j]; // 如果第j位上為1,即(m & nums[i])!= 0,則對應vec[j]+1
m <<= 1;
}
}
unsigned int res = 0;
for (int i = 31; i >= 0; --i) { // 將vec各元素對3求餘,結果為"只出現一次的數字"的各二進位制位上的數。
res <<= 1;
res |= vec[i] % 3; // 恢復第i位的值到res(從高位到底位)
}
return res;
}
};
劍指 Offer 57. 和為s的兩個數字
題目中給的遞增(有序)直接可以想到二分
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
vector<int>res;
if(nums.size()==0) return res;
int left = 0, right = nums.size()-1;
while(left<right){
if(nums[left]+nums[right]==target){//當找到和為S的數時
res.resize(2);
res[0] = nums[left];
res[1] = nums[right];
break;
}
if(nums[left]+nums[right]>target) right--;//大於S時範圍大了向左收縮
else if(nums[left+nums[right]<target]) left++;
}
return res;
}
};
劍指 Offer 57 - II. 和為s的連續正數序列
要輸出所有和為S的,思路就是利用一個左邊界和右邊界,當小於目標值時左邊界向右滑動,當大於目標值時,右邊界向左滑動
滑動視窗
class Solution {
public:
vector<vector<int>> findContinuousSequence(int target) {
int i = 1; // 滑動視窗的左邊界
int j = 1; // 滑動視窗的右邊界
int sum = 0; // 滑動視窗中數字的和
vector<vector<int>> res;
while (i <= target / 2) {//左邊界一定小於等於target的一半,不然一定大於target
if (sum < target) {
// 右邊界向右移動
sum += j;
j++;
} else if (sum > target) {
// 左邊界向右移動
sum -= i;
i++;
} else {
// 記錄結果
vector<int> arr;
for (int k = i; k < j; k++) {
arr.push_back(k);
}
res.push_back(arr);
// 左邊界向右移動
sum -= i;
i++;
}
}
return res;
}
};