8 分治算法
8.1 【遞歸】劍指 Offer 07 - 重建二叉樹
https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/
??前序遍歷是根左右,中序遍歷是左根右,這也就意味著前序遍歷的第一個節(jié)點是整棵樹的根節(jié)點,順著這個節(jié)點找到它在中序遍歷中的位置,即為in_root,那么in_root左邊的都在左子樹,右邊的都在右子樹,這樣就可以知道左子樹一共有多少個節(jié)點,然后去前序遍歷中找到左右子樹的分界點,分成左右兩部分,分別重復上述過程,找到各自部分的第一個根節(jié)點,然后再依次往下進行,直到最后左右子樹的邊界發(fā)生重合,此時二叉樹重建完畢。
/**
* 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 {
private:
unordered_map<int,int> hash_table;
public:
TreeNode* subTree(vector<int>& preorder, vector<int>& inorder, int pre_l, int pre_r, int in_l, int in_r)
{
if(pre_l > pre_r) return nullptr;
//找到根節(jié)點,計算左子樹的節(jié)點數(shù)
int pre_root = pre_l;
int in_root = hash_table[preorder[pre_l]];
int sub_l = in_root - in_l;
//開始生成root
TreeNode* root = new TreeNode(preorder[pre_l]);
//對于左子樹而言,前序遍歷的[左邊界+1,左邊界+左子樹節(jié)點數(shù)]即為中序遍歷的[左邊界,根節(jié)點-1]
root->left = subTree(preorder, inorder, pre_l+1, pre_l+sub_l, in_l, in_root-1);
//對于右子樹而言,前序遍歷的[左邊界+左子樹節(jié)點數(shù)+1,右邊界]即為中序遍歷的[根節(jié)點+1,右邊界]
root->right = subTree(preorder, inorder, pre_l+sub_l+1, pre_r, in_root+1, in_r);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
{
int n = inorder.size();
for(int i=0;i<n;i++)
{
hash_table[inorder[i]] = i;
}
return subTree(preorder, inorder, 0, n-1, 0, n-1);
}
};
8.2 【遞歸】【快速冪】劍指 Offer 16 - 數(shù)值的整數(shù)次方
https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/description/
??這道題可以用快速冪來解決,具體思路就是把冪次按照二進制拆開,分別計算,下面舉個例子: 假設我要計算 x 10 x^{10} x10,因為10的二進制表示為1010,那么 x 10 = x 2 0 ? 0 ? x 2 1 ? 1 ? x 2 2 ? 0 ? x 2 3 ? 1 x^{10}=x^{2^0*0}*x^{2^1*1}*x^{2^2*0}*x^{2^3*1} x10=x20?0?x21?1?x22?0?x23?1,可以看作按照x的2次方依次遞增,只需要看這一個次方對應的二進制是否為1。
class Solution {
public:
double myPow(double x, int n) {
if(x==1 || n==0) return 1;
if(x==0) return 0;
double ans = 1;
long num = n;
if(n<0)
{
num = -num;
x = 1/x;
}
while(num)
{
if(num & 1) ans *= x;
x *= x;
num >>= 1;
}
return ans;
}
};
8.3 【遞歸】劍指 Offer 33 - 二叉搜索樹的后序遍歷序列
https://leetcode.cn/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof
??后序遍歷的特點就是先訪問左子樹再訪問右子樹最后訪問根節(jié)點,所以最后一個元素一定根節(jié)點,而二叉搜索樹的特點就是左子樹<根節(jié)點<右子樹,所以我們可以根據(jù)根節(jié)點的值把數(shù)組分為兩部分,然后分別判斷是否符合二叉搜索樹的特點,重復上述過程直到所有情況都判斷完為止。
class Solution {
public:
bool dfs(vector<int>& postorder, int left, int right)
{
if(left >= right) return true;
int i = left;
while(postorder[i]<postorder[right])
{
i++;
}
int mid = i;
while(postorder[i]>postorder[right])
{
i++;
}
return i==right & dfs(postorder,left,mid-1) & dfs(postorder,mid,right-1);
}
bool verifyPostorder(vector<int>& postorder)
{
return dfs(postorder,0,postorder.size()-1);
}
};
8.4 【遞歸】【分治】劍指 Offer 17 - 打印從1到最大的n位數(shù)
https://leetcode.cn/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof
??這道題目由于指定了數(shù)組是int型,所以不用考慮大整數(shù)問題,直接暴力就可以解決,但如果是大整數(shù)的話,需要采用分治遞歸的思想,主要如下:
(1)第一層遍歷,為n,分別考慮到生成的數(shù)字是1位數(shù)、2位數(shù)、3位數(shù)…n位數(shù);
(2)第二層遍歷,分別遍歷每一位數(shù)是幾,除了第一位是1-9之外,其余都是0-9。
class Solution {
public:
vector<int> printNumbers(int n) {
int cnt = pow(10,n);
vector<int> ans;
for(int i=1;i<cnt;i++)
{
ans.push_back(i);
}
return ans;
}
};
8.5 【歸并排序】【分治】劍指 Offer 51 - 數(shù)組中的逆序對
https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof
??這位大佬寫的很好?。╤ttps://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solutions/622496/jian-zhi-offer-51-shu-zu-zhong-de-ni-xu-pvn2h)
class Solution {
public:
int merge_sort(int left, int right, vector<int>& nums, vector<int>& tmp)
{
if(left >= right) return 0;
int mid = (left + right) / 2;
int res = merge_sort(left, mid, nums, tmp) + merge_sort(mid+1, right, nums, tmp);
int i = left, j = mid + 1;
for(int k=left;k<=right;k++)
{
tmp[k] = nums[k];
}
for(int k=left;k<=right;k++)
{
if(i == mid+1) nums[k] = tmp[j++];
else if(j == right+1 || tmp[i] <= tmp[j]) nums[k] = tmp[i++];
else
{
nums[k] = tmp[j++];
res += mid - i + 1;
}
}
return res;
}
int reversePairs(vector<int>& nums)
{
vector<int> tmp(nums.size());
return merge_sort(0, nums.size()-1, nums, tmp);
}
};
9 排序
9.1 【冒泡排序】劍指 Offer 45 - 把數(shù)組排成最小的數(shù)
https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof
??這題可以看作是冒泡排序,只不過針對的對象是字符串,我們需要找到里面表示最大的字符串,然后把它放到字符串的最后即可,例如“32”和“3”,因為“323”<“332”,所以“32”<“3”,所以應該把“3”放在“32”后面,借助這種排序思路來解題。
class Solution {
public:
string minNumber(vector<int>& nums)
{
for(int i=nums.size()-1;i>0;i--)
{
for(int j=0;j<i;j++)
{
if(string_sort(nums[j],nums[j+1]))
{
swap(nums[j],nums[j+1]);
}
}
}
string ans = "";
for(int i=0;i<nums.size();i++)
{
ans += to_string(nums[i]);
}
return ans;
}
bool string_sort(int num1, int num2)
{
string s1 = to_string(num1) + to_string(num2);
string s2 = to_string(num2) + to_string(num1);
if(s1>s2) return true;
else return false;
}
};
9.2 【排序】劍指 Offer 61 - 撲克牌中的順子
https://leetcode.cn/problems/bu-ke-pai-zhong-de-shun-zi-lcof
??這道題不難,想清楚順子的判斷條件即可,主要為以下幾個方面:
(1)五個數(shù)中除了0以外不能有其他的重復數(shù)字,否則肯定不是順子;
(2)找出五個數(shù)中的最大值和最小值,看看這兩個數(shù)之間缺的數(shù)的個數(shù)是多少,記為gap,然后計算五個數(shù)中0的個數(shù),記為zero_num,如果gap<=zero_num,說明0的個數(shù)可以補全缺的數(shù),否則就肯定不是順子。
class Solution {
public:
bool isStraight(vector<int>& nums) {
int arr[14] = {0};
int max_num = 0, min_num = 14, zero_num = 0;
for(int i=0;i<5;i++)
{
if(nums[i]==0) zero_num++;
else
{
if(arr[nums[i]]) return false;
else
{
arr[nums[i]] = 1;
min_num = min(min_num, nums[i]);
max_num = max(max_num, nums[i]);
}
}
}
int gap = 0;
for(int i=min_num;i<max_num;i++)
{
if(arr[i]!=1) gap++;
}
if(gap <= zero_num) return true;
else return false;
}
};
9.3 【堆排序】劍指 Offer 40 - 最小的k個數(shù)
https://leetcode.cn/problems/zui-xiao-de-kge-shu-lcof
??方法一:直接用sort排序,然后選前k個數(shù)就好了。
??方法二:用堆排序,堆排序,采用大根堆,首先把前k個數(shù)字存入大根堆中,之后的數(shù)字依次和大根堆的top比較,如果比top小就更新大根堆,最后把大根堆中的數(shù)字存入vector中作為答案返回。
//方法一:直接排序
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
sort(arr.begin(),arr.end());
vector<int> ans;
for(int i=0;i<k;i++)
{
ans.push_back(arr[i]);
}
return ans;
}
};
//方法二:堆排序
class Solution {
public:
vector<int> getLeastNumbers(vector<int>& arr, int k) {
vector<int> ans;
if(k == 0) return ans;
priority_queue<int, vector<int>, less<int>> max_stack;
//把前k個數(shù)字存入大根堆
for(int i=0;i<k;i++)
{
max_stack.push(arr[i]);
}
//依次和大根堆的top比較,如果比top小就更新大根堆
for(int j=k;j<arr.size();j++)
{
if(arr[j] < max_stack.top())
{
max_stack.pop();
max_stack.push(arr[j]);
}
}
//把大根堆中的數(shù)字存入vector中作為答案返回
while(k--)
{
ans.push_back(max_stack.top());
max_stack.pop();
}
return ans;
}
};
9.4 【堆排序】【優(yōu)先隊列】劍指 Offer 41 - 數(shù)據(jù)流中的中位數(shù)
https://leetcode.cn/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof
??這道題如果直接對所有數(shù)進行排序的話,最后會runtime,其實只要能找出每一次執(zhí)行findMedian函數(shù)時整個數(shù)據(jù)流中間的兩個數(shù)或者一個數(shù)就行了,這是我們可以考慮用堆排序,同時維持一個大根堆和一個小根堆,把數(shù)據(jù)流中的數(shù)分為兩部分,同時也要保證大根堆中的top要比小根堆中的top小,這樣最后的中位數(shù)要么是小根堆top,要么就是大根堆和小根堆的top取平均,構造方法如下:
- 如果大根堆和小根堆的size一樣,那么此時add的num就加入大根堆,然后把大根堆的top插入到小根堆中,保證大根堆的size<=小根堆的size;
- 如果大根堆和小根堆的size不一樣,那么此時add的num就加入小根堆,然后把小根堆的top插入到大根堆中,保證小根堆的size<=大根堆的size;
- 執(zhí)行findMedian函數(shù)時,看看此時大根堆和小根堆的size是否相等,如果相等的話,中位數(shù)就是各自取top元素相加取平均,如果不相等,那么中位數(shù)就是小根堆的top。
class MedianFinder {
public:
/** initialize your data structure here. */
priority_queue<int, vector<int>, greater<int>> min_stack;
priority_queue<int, vector<int>, less<int>> max_stack;
MedianFinder() {
}
void addNum(int num) {
//如果大根堆和小根堆的size一樣,那么此時add的num就加入大根堆,然后把大根堆的top插入到小根堆中,保證大根堆的size<=小根堆的size
if(min_stack.size() == max_stack.size())
{
max_stack.push(num);
min_stack.push(max_stack.top());
max_stack.pop();
}
//如果大根堆和小根堆的size不一樣,那么此時add的num就加入小根堆,然后把小根堆的top插入到大根堆中,保證小根堆的size<=大根堆的size
else
{
min_stack.push(num);
max_stack.push(min_stack.top());
min_stack.pop();
}
}
//執(zhí)行findMedian函數(shù)時,看看此時大根堆和小根堆的size是否相等,如果相等的話,中位數(shù)就是各自取top元素相加取平均,如果不相等,那么中位數(shù)就是小根堆的top
double findMedian() {
if(min_stack.size() == max_stack.size())
{
return (max_stack.top() + min_stack.top()) / 2.0;
}
else return min_stack.top();
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
10 動態(tài)規(guī)劃
10.1 【動態(tài)規(guī)劃】【哈希表】【DFS】劍指 Offer 10- I - 斐波那契數(shù)列
https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof
??這道題如果直接用動態(tài)規(guī)劃會runtime,主要是因為在計算過程中會有一些數(shù)被反復計算,所以我們在這里采用哈希表來存放已經(jīng)被計算過的數(shù),這樣在之后再次被計算時直接用就好了。
class Solution {
public:
unordered_map<int,int> hash_table;
int dfs(int n)
{
if(n == 0) return 0;
else if(n == 1) return 1;
else
{
if(hash_table.count(n)) return hash_table[n];
else
{
int num1 = dfs(n-1) % 1000000007;
int num2 = dfs(n-2) % 1000000007;
hash_table[n] = (num1 + num2) % 1000000007;
return hash_table[n];
}
}
}
int fib(int n)
{
return dfs(n);
}
};
10.2 【動態(tài)規(guī)劃】【哈希表】【DFS】劍指 Offer 10- II - 青蛙跳臺階問題
https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof
??這道題目就是變形的斐波那契數(shù)列,在這里采用哈希表來存放已經(jīng)被計算過的數(shù),這樣在之后再次被計算時直接用就好了。
class Solution {
public:
unordered_map<int,int> hash_table;
int dfs(int n)
{
if(n == 0) return 1;
else if(n == 1) return 1;
else
{
if(hash_table.count(n)) return hash_table[n];
else
{
int num1 = dfs(n-1) % 1000000007;
int num2 = dfs(n-2) % 1000000007;
hash_table[n] = (num1 + num2) % 1000000007;
return hash_table[n];
}
}
}
int numWays(int n) {
return dfs(n);
}
};
10.3 【動態(tài)規(guī)劃】劍指 Offer 63 - 股票的最大利潤
https://leetcode.cn/problems/gu-piao-de-zui-da-li-run-lcof
??動態(tài)規(guī)劃類題目的解題主要是找到狀態(tài)轉移方程就好了,對于這道題目的狀態(tài)轉移就在于某一天有無股票,我們以此為分界來定義狀態(tài)轉移方程dp[i][2]:
(1)前i天未持有股票
d p [ i ] [ 0 ] = m a x ( d p [ i ? 1 ] [ 0 ] , d p [ i ? 1 ] [ 1 ] + p r i c e s [ i ] ) dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) dp[i][0]=max(dp[i?1][0],dp[i?1][1]+prices[i])
(2)前i天持有股票
d p [ i ] [ 1 ] = m a x ( d p [ i ? 1 ] [ 1 ] , 0 ? p r i c e s [ i ] ) dp[i][1] = max(dp[i-1][1], 0 - prices[i]) dp[i][1]=max(dp[i?1][1],0?prices[i])
??同時還要預判一下prices為空的情況,此時返回0,因為dp的兩個元素在反復調用,所以在代碼中也是直接用兩個變量來進行代替了。
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(!prices.size()) return 0;
int dp_0 = 0, dp_1 = -prices[0];
for(int i=1;i<prices.size();i++)
{
dp_0 = max(dp_0, dp_1 + prices[i]);
dp_1 = max(dp_1, 0 - prices[i]);
}
return dp_0;
}
};
10.4 【動態(tài)規(guī)劃】【分治】劍指 Offer 42 - 連續(xù)子數(shù)組的最大和
https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof
??這道題用到一點點分治的思想,假設現(xiàn)在數(shù)組長度為 n n n,連續(xù)子數(shù)組的最大和為 f ( n ) f(n) f(n),那么 f ( n ) = m a x ( f ( n ? 1 ) + n u m s [ i ] , n u m [ i ] ) f(n)=max(f(n-1)+nums[i],num[i]) f(n)=max(f(n?1)+nums[i],num[i]),也就意味著最大和要么是前 n ? 1 n-1 n?1個數(shù)的最大和加上第 i i i個數(shù),要么就是第 i i i個數(shù)本身,如果是第 i i i個數(shù)本身的話,就要從這里開始重新找到連續(xù)子數(shù)組了,在這個過程中記錄下最大值即可。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int pre = 0, max_seqsum = nums[0];
for(int i=0;i<nums.size();i++)
{
pre = max(pre + nums[i], nums[i]);
max_seqsum = max(max_seqsum, pre);
}
return max_seqsum;
}
};
10.5 【動態(tài)規(guī)劃】劍指 Offer 47 - 禮物的最大價值
https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof文章來源:http://www.zghlxwxcb.cn/news/detail-681874.html
??其實每個地方的最大值只與兩個狀態(tài)有關,假設目前要求的是 v a l u e [ i ] [ j ] value[i][j] value[i][j]的最大值,那么 v a l u e [ i ] [ j ] = m a x ( v a l u e [ i ] [ j ? 1 ] , v a l u e [ i ? 1 ] [ j ] ) + g r i d [ i ] [ j ] value[i][j] = max(value[i][j-1],value[i-1][j]) + grid[i][j] value[i][j]=max(value[i][j?1],value[i?1][j])+grid[i][j],這里為了方便初始化,把初始數(shù)組的大小設置為 ( m + 1 ) ? ( n + 1 ) (m+1)*(n+1) (m+1)?(n+1)。文章來源地址http://www.zghlxwxcb.cn/news/detail-681874.html
class Solution {
public:
int maxValue(vector<vector<int>>& grid)
{
int m = grid.size(), n = grid[0].size();
int value[m+1][n+1];
memset(value,0,sizeof(value));
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
value[i+1][j+1] = max(value[i+1][j],value[i][j+1]) + grid[i][j];
}
}
return value[m][n];
}
};
到了這里,關于【leetcode刷題之路】劍指Offer(4)——分治+排序算法+動態(tài)規(guī)劃的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!