一. 不同路徑
1. 題目描述
一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記爲 “Start” )。
機器人每次只能 “向下或者向右” 移動一步。機器人試圖達到網格的右下角(在下圖中標記爲 “Finish” )。
問總共有多少條不同的路徑?
示例:
leetcode:https://leetcode.cn/problems/unique-paths/description/
難度:【中等】
2. 思路分析
(1). 定義一個二維數組dp[i][j],表示從起點到網格的 (i, j) 點的不同路徑數. 需要默認全部初始化爲0
(2). 初始化狀態,將第一行和第一列中初始化爲1
(3). 確定狀態轉移方程
dp[i][j] 等於上面方格 和 左面方格的和, 即 dp[i][j]=dp[i-1][j]+dp[i][j-1]
(4). 返回最終結果
dp[m-1][n-1]
3. 代碼實操
/**
* 求不同路徑的總條數
* @param m m行
* @param n n列
* @returns 返回到右下角的條數
*/
function uniquePaths(m: number, n: number): number {
//1. 定義狀態
let dp: number[][] = [];
//用0填充這個二維數組,否則undefined下面會報錯
for (let i = 0; i < m; i++) {
let temp: number[] = [];
for (let j = 0; j < n; j++) {
temp.push(0);
}
dp.push(temp);
}
// console.log(dp);
//2. 初始化狀態
for (let i = 0; i < m; i++) {
dp[i][0] = 1; //第一列
}
for (let j = 0; j < n; j++) {
dp[0][j] = 1; //第一行
}
//3.確定狀態轉移方程
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
}
}
//4. 返回最終結果
return dp[m - 1][n - 1];
}
/**
* 求不同路徑的總條數--簡潔代碼
* @param m m行
* @param n n列
* @returns 返回到右下角的條數
*/
function uniquePaths2(m: number, n: number): number {
//1. 定義狀態
// let dp: number[][] = Array.from({ length: m }, () => new Array(n).fill(0));
const dp: number[][] = new Array(m).fill(new Array(n).fill(0));
//2. 初始化狀態
dp[0][0] = 1;
//3.確定狀態轉移方程
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (i == 0 || j == 0) {
dp[i][j] = 1;
continue; //直接進行下一次for循環
}
dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
}
}
//4. 返回最終結果
return dp[m - 1][n - 1];
}
{
//測試1
console.log(uniquePaths(7, 3)); //28
console.log(uniquePaths(3, 3)); //6
//測試2
console.log(uniquePaths2(7, 3)); //28
}
二. 禮物最大價值
1. 題目描述
現有一個記作二維矩陣 frame 的珠寶架,其中 frame[i][j] 爲該位置珠寶的價值。拿取珠寶的規則爲:
只能從架子的【左上角】開始拿珠寶,每次可以移動到【右側】或【下側】的相鄰位置,到達珠寶架子的【右下角】時,停止拿取
注意:珠寶的價值都是大於 0 的。除非這個架子上沒有任何珠寶,比如 frame = [[0]]。
示例:
leetcode:https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/description/
難度:【中等】
2. 思路分析
(核心點:當前格子禮物的最大價值=當前格子禮物的價值 + Math.Max(上面格子最大最大價值,左面格子的最大價值))
(1).定義狀態
dp[i][j]表示當前格子禮物的最大價值
(2).初始化狀態
將第一行和第一列都初始化了
(3).確定狀態轉移方程
dp[i][j] = frame[i][j] + Math.max(dp[i][j - 1], dp[i - 1][j]);
(4). 返回最終結果
dp[m-1][n-1]
3. 代碼實操
/**
* 求禮物的最大價值
* @param frame frame[i][j] 爲該位置珠寶的價值
* @returns
*/
function jewelleryValue(frame: number[][]): number {
//1. 定義狀態
let m = frame.length; //m行
let n = frame[0].length; //n列
let dp: number[][] = Array.from({ length: m }, () => new Array(n).fill(0)); //填充爲0
//2. 初始化狀態
dp[0][0] = frame[0][0];
for (let i = 1; i < n; i++) {
dp[0][i] = dp[0][i - 1] + frame[0][i]; //第一行
}
for (let j = 1; j < m; j++) {
dp[j][0] = dp[j - 1][0] + frame[j][0]; //第一列
}
//3. 狀態轉移方程
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
dp[i][j] = frame[i][j] + Math.max(dp[i][j - 1], dp[i - 1][j]);
}
}
//4.返回最終結果
return dp[m - 1][n - 1];
}
//測試
console.log(
jewelleryValue([
[1, 3, 1],
[1, 5, 1],
[4, 2, 1],
])
);
三. 最長遞增子序列
一. 題目描述
給你一個整數數組 nums ,找到其中最長嚴格遞增子序列的“長度”。
子序列 是由數組派生而來的序列,刪除(或不刪除)數組中的元素而不改變其餘元素的順序。
例如,[3,6,2,7] 是數組 [0,3,1,6,2,2,7] 的子序列(不是遞增的!!)
leetCode:https://leetcode.cn/problems/longest-increasing-subsequence/description/
難度:【中等】
二. 思路分析-動態規劃
1. 定義狀態:設dp[i]表示以第i個元素結尾的最長上升子序列的長度。
2. 初始狀態:對於每個i,dp[i]的初始值爲1,因爲每個元素本身也可以作爲一個長度爲1的上升子序列。
3. 確定狀態轉移方程:
(1).對於每個i,我們需要找到在[0, i-1]範圍內比nums[i]小的元素,以這些元素結尾的最長上升子序列中最長的那個子序列的長度。
(2).然後將其加1即可得到以nums[i]結尾的最長上升子序列的長度。
(3).狀態轉移方程爲:
dp[i] = Math.max(dp[j] + 1, dp[i]); 其中j < i且nums[j] <nums[i]。
(4). 返回最終結果
A.可以直接求數組最大值
B.或者用個max變量進行存放
三. 代碼實操-動態規劃
/**
* 寫法1
* @param nums
* @returns
*/
function lengthOfLIS(nums: number[]): number {
//1. 定義狀態 2. 初始化狀態
let n = nums.length;
let dp: number[] = new Array(n).fill(1);
//3.確定狀態轉移方程
for (let i = 1; i < n; i++) {
for (let j = 0; j <= i - 1; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[j] + 1, dp[i]); //核心
}
}
}
//4.返回最終結果
return Math.max(...dp);
}
/**
* 寫法2--提出來最大值
* @param nums
* @returns
*/
function lengthOfLIS2(nums: number[]): number {
//1. 定義狀態 2. 初始化狀態
let n = nums.length;
let dp: number[] = new Array(n).fill(1);
//3.確定狀態轉移方程
let max = dp[0];
for (let i = 1; i < n; i++) {
for (let j = 0; j <= i - 1; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[j] + 1, dp[i]); //核心
max = Math.max(dp[i], max);
}
}
}
//4.返回最終結果
return max;
}
四. 思路分析-貪心+二分
1. 維護一個數組tails,用於記錄掃描到的元素應該存放的位置。
2. 掃描原數組中的每個元素num,和tails數組中最後一個元素的進行比較。
(1).如果該num更小,則用該num覆蓋tails中的最後一個元素
(2).如果該num更大,則將該num插入到tails的尾部
3. tails數組的長度,就是最長遞增子序列的長度
補充-爲什麼這麼神奇剛好是數組的長度呢?(瞭解)
◼ 情況一:如果是逆序的時候,一定會一直在一個上面加加加
◼ 情況二:一旦出現了比前面的最小值的值大的,那麼就一定會增加一個新的數列,說明在上升的過程
◼ 情況三:如果之後出現一個比前面數列小的,那麼就需要重新計算序列:
五. 代碼實操-動態規劃
/**
* 求最長遞增子序列的長度
* @param nums 數組序列
* @returns 返回最長遞增子序列的長度
*/
function lengthOfLIS(nums: number[]): number {
//1.定義變量
let n = nums.length;
let tails: number[] = [];
//2.遍歷nums數組
for (let i = 0; i < n; i++) {
const current = nums[i];
//3. 進行二分查找
let left = 0;
let right = tails.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2); //下整
if (current <= tails[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
//4. 判斷是否找到元素
if (left == tails.length) {
//表示沒有找到
tails.push(current);
} else {
tails[left] = current;
}
}
return tails.length;
}
//測試
{
console.log(lengthOfLIS([10, 9, 2, 4, 3, 7, 101, 18])); //4
console.log(lengthOfLIS([7, 7, 7, 7, 7, 7, 7])); //1
}
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。