本系列主要記錄 LeetCode
經典演算法題的解題記錄以及從看到題目到解出答案,最終多思維擴充的的過程。僅作為題目記錄以及解題思路探討(希望大家可以一起溝通解題思路),以管中窺豹的方式學習演算法!
目前瞭解並瀏覽過的書籍推薦漫畫演算法
、資料結構
注:
貼出來的程式碼為了方便,一個思路中可能多個邏輯解法,如需複製執行注意註釋說明
迴文數
題目描述:
題意拆解:
判斷一串數字是否是迴文數,返回boolean
值
- 迴文數(迴文數是指正序(從左向右)和倒序(從右向左)讀都是一樣的整數。例如,121 是迴文,而 123 不是。)
思路一:遍歷倒敘
判斷是否和原始值完全一致
var isPalindrome = function(x) {
if (x < 0) return false;
// 思路一:正序和倒敘的值是完全一致的則是迴文
// 這裡可利用遍歷、倒敘、等陣列/字串的原生方法
// 利用 reverse (76-80ms)
x = x.toString();
let reverse_x = x.split("").reverse().join("");
return x === reverse_x;
// 遍歷 (60-84ms)
x = x.toString().split("");
let reverse_x = '';
for (let i = 0; i < x.length; i++) {
const ele = x[i];
reverse_x = ele + reverse_x;
}
return reverse_x === x.join('');
};
思路二:基於數字特性的進位制替換
既然是數字可以利用進位制替換(60-92ms)
// 思路二:既然是數字可以利用進位制替換(60-92ms)
var isPalindrome = function(x) {
let reverse_x = 0;
for (let i = x; i >= 1; i = Math.floor(i / 10)){
reverse_x = reverse_x * 10 + (i % 10);
}
return reverse_x === x;
// 既然是迴文那麼我只遍歷一半呢
let reverse_x = 0;
let center_index,
minVal = 1;
let x_leng = x.toString().length;
center_index = Math.ceil(x_leng / 2);
minVal = Math.pow(10, center_index);
for (let i = x; i >= minVal; i = Math.floor(i / 10)) {
reverse_x = reverse_x * 10 + (i % 10);
}
return reverse_x === Math.floor(x / minVal);
// 利用 which 替換for
if (x < 0) return false;
let temp = x;
let res = 0;
while (x) {
res = res * 10 + (x % 10);
x = Math.floor(x / 10);
}
return res == temp;
};
此處迴文的只是數字利用數字位替換,判斷連個位數知否相等;既然是迴文那麼我們只遍歷一般數字判斷遍歷的前半部分和未處理的後半部分是否一致即可
合併K個升序連結串列
題目描述:
題意拆解:
將多個升序連結串列合併成一個升序連結串列
思路一:利用陣列的遍歷的方式
//連結串列節點例項
function ListNode(val, next) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
var mergeKLists = function(lists) {
// 思路一:利用陣列的處理方式(不推薦)
// 亮點:程式碼簡介
return lists.reduce((p, n) => {
while (n) {
p.push(n), n = n.next
}
return p
},[]).sort((a, b) => a.val - b.val).reduceRight((p, n) => (n.next = p, p = n, p), null)
// 亮點: 條理清晰
// 1. 想將連結串列處理成一維陣列,
// 2. 將陣列進行排序
// 3. 最後將排序後的陣列轉換成連結串列
let arr = []
lists.forEach((ele) => {
function deepLinkedList(list) {
arr.push(list.val);
if(list.next){
deepLinkedList(list.next);
}
}
deepLinkedList(ele)
});
arr.sort()
let arr_list = {};
function deepList(arr,map) {
map["val"] = arr[0];
arr.shift(1);
map["next"] = arr.length?{}:null;
if(arr.length){
deepList(arr, map["next"]);
}
}
deepList(arr, arr_list);
return arr_list;
};
思路二:連結串列兩兩合併
function ListNode(val, next) {
this.val = (val===undefined ? 0 : val)
this.next = (next===undefined ? null : next)
}
var mergeKLists = function(lists) {
// 思路二:使用資料結構連結串列思路:新建一個空白連結串列以及一個移動的指標指向將要操作的節點上
// 兩兩合併
let ans = null;
for (let i = 0; i < lists.length; i++) {
ans = mergeTwoList(ans, lists[i]);
}
return ans;
function mergeTwoList(pHead1, pHead2) {
let newList = new ListNode();
let cur = newList;
while (pHead1 !== null && pHead2 !== null) {
// 找出更小篩入佇列中
if(pHead1.val < pHead2.val) {
cur.next = pHead1
pHead1 = pHead1.next
} else {
cur.next = pHead2
pHead2 = pHead2.next
}
cur = cur.next;
}
cur.next = pHead1 || pHead2;
// console.log(newList,cur);
return newList.next;
}
};
測試用例:
let data = mergeKLists([
{ val: 1, next: { val: 4, next: { val: 5, next: null } } },
{ val: 1, next: { val: 3, next: { val: 4, next: null } } },
{ val: 2, next: { val: 6, next: null } },
]);
K 個一組翻轉連結串列
題目描述:
題意拆解:
將一個連結串列按n
未一組倒敘後返回,如果剩餘連結串列或總連結串列不足n
位時保持原狀
思路一:利用連結串列轉陣列,陣列轉連結串列的暴力方式
var reverseKGroup = function(head, k) {
if (!k) return;
// 思路一:利用連結串列轉陣列,陣列轉連結串列的暴力方式
let arr = [];
let len = 0;
let index = 0;
function deepLinkedList(list) {
++len;
if (!arr[index]) arr[index] = [];
arr[index].push(list.val);
if (len === k) {
len = 0;
index += 1;
}
if (list.next) {
deepLinkedList(list.next);
}
}
// 將連結串列按序轉成二維陣列
deepLinkedList(head);
// 翻轉陣列並整平
arr = arr.map((item) => {
if (item.length >= k) {
return item.reverse();
}
return item;
});
arr = [].concat.apply([], arr);
// 陣列在轉換成連結串列
let arr_list = {};
function deepList(arr,map) {
map["val"] = arr[0];
arr.shift(1);
map["next"] = arr.length?{}:null;
if(arr.length){
deepList(arr, map["next"]);
}
}
deepList(arr, arr_list);
return arr_list;
};
思路一:利用連結串列轉陣列,陣列轉連結串列的暴力方式
var reverseKGroup = function(head, k) {
if (!k) return;
// 思路一:利用連結串列轉陣列,陣列轉連結串列的暴力方式
// let arr = [];
// let len = 0;
// let index = 0;
// function deepLinkedList(list) {
// ++len;
// if (!arr[index]) arr[index] = [];
// arr[index].push(list.val);
// if (len === k) {
// len = 0;
// index += 1;
// }
// if (list.next) {
// deepLinkedList(list.next);
// }
// }
// // 將連結串列按序轉成二維陣列
// deepLinkedList(head);
// // 翻轉陣列並整平
// arr = arr.map((item) => {
// if (item.length >= k) {
// return item.reverse();
// }
// return item;
// });
// arr = [].concat.apply([], arr);
// // 陣列在轉換成連結串列
// let arr_list = {};
// function deepList(arr,map) {
// map["val"] = arr[0];
// arr.shift(1);
// map["next"] = arr.length?{}:null;
// if(arr.length){
// deepList(arr, map["next"]);
// }
// }
// deepList(arr, arr_list);
// return arr_list;
// 思路二:對連結串列進行操作
const hair = new ListNode();
hair.next = head;
let pre = hair;
while (head) {
let tail = pre;
// 如果分組的值大於整個鏈長則直接將鏈返回
for (let i = 0; i < k; ++i) {
tail = tail.next; // 從 0 開始
if (!tail) {
return hair.next;
}
}
const nex = tail.next; // 把除去當前組的剩餘連結串列儲存
console.log("倒敘前", head, tail);
[head, tail] = myReverse(head, tail);
// 把子連結串列重新接回原連結串列
console.log("倒敘後", head, tail, JSON.stringify(pre));
pre.next = head;
tail.next = nex;
console.log(JSON.stringify(pre), nex, tail);
pre = tail;
head = tail.next;
}
console.log("結果:", hair.next === head, head, JSON.stringify(hair));
return hair.next;
};
思路二:連結串列分組並翻轉
var reverseKGroup = function(head, k) {
if (!k) return;
// 思路二:對連結串列進行操作
const hair = new ListNode();
hair.next = head;
let pre = hair;
while (head) {
let tail = pre;
// 如果分組的值大於整個鏈長則直接將鏈返回
for (let i = 0; i < k; ++i) {
tail = tail.next; // 從 0 開始
if (!tail) {
return hair.next;
}
}
const nex = tail.next; // 把除去當前組的剩餘連結串列儲存
console.log("倒敘前", head, tail);
[head, tail] = myReverse(head, tail);
// 把子連結串列重新接回原連結串列
console.log("倒敘後", head, tail, JSON.stringify(pre));
pre.next = head;
tail.next = nex;
console.log(JSON.stringify(pre), nex, tail);
pre = tail;
head = tail.next;
}
console.log("結果:", hair.next === head, head, JSON.stringify(hair));
return hair.next;
};
測試用例
reverseKGroup({ val: 1, next: { val: 2, next: { val: 3, next: { val: 4, next: null } } } },2);
移動零
題目描述:
題意拆解:
將陣列中的0
陣列移動到最右側,其他非零數字順序不變但向前補位
思路一: 操作原陣列覆蓋修改,記錄零的個數並置零,時間複雜度增多
var moveZeroes = function(nums) {
// 要求: 要求只能原陣列操作
// 思路一: 操作原陣列覆蓋修改,記錄零的個數並置零,時間複雜度增多
var k = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] != 0){
nums[k++] = nums[i];
}
}
for (let j = k; j < nums.length; j++){
nums[j] = 0;
}
};
思路二:雙指標思路(冒泡的思路)
var moveZeroes = function(nums) {
// 要求: 要求只能原陣列操作
// 思路二:雙指標思路(冒泡的思路)
// 以 0 作為入口 兩次迴圈
for (let i = 0; i < nums.length; i++) {
const ele = nums[i];
if(ele === 0){
for (let j = i+1; j < nums.length; j++) {
const ele2 = nums[j];
if (ele2 !== 0) {
nums[i] = ele2;
nums[j] = ele;
break;
}
}
}
}
// 以不等於 0 作為入口;一次迴圈,並新增一個計數器
let j = 0;
for (let i = 0; i < nums.length; i++) {
//當前元素!=0,就把其交換到左邊,等於0的交換到右邊
if (nums[i] != 0) {
let tmp = nums[i]; // 臨時存下來
nums[i] = nums[j];
nums[j++] = tmp;
}
}
// 思考一下,如果按上面的思路以等於 0 作為判斷依據並只遍歷一遍呢?
}
測試用例
課程表 IV
題目描述:
題意拆解:
提供一個長度的數字並有一份各個數字之間關聯關係的對映,判斷一組關係是否符合
思路一: 暴力遞迴
遞迴遍歷找出各個課程的父課程 會超時! 換了兩種遍歷途徑還是都會超時
var checkIfPrerequisite = function(numCourses, prerequisites, queries) {
if (!prerequisites.length) {
return queries.map(() => {
return false;
});
}
// 思路一: 遞迴遍歷找出各個課程的父課程 會超時! 換了兩種遍歷途徑還是都會超時
let allQueries = [];
// prerequisites = prerequisites.sort((a,b)=>{
// return a[0] - b[0]
// })
// prerequisites.forEach((ele,index) => {
// allQueries.push(ele);
// let curPreVal = ele[0];
// let curNextVal = ele[1]
function deep(curVal,oldVal,idx) {
if (curVal < numCourses) {
// 這裡做個小優化如果吧二維陣列的第一個陣列排序那每次遞迴遍歷只需要從當前索引的下一個來即可
for (let i = 0; i < prerequisites.length; i++) {
const [j,k] = prerequisites[i];
if (j === curVal) {
allQueries.push([oldVal===null?curVal:oldVal, k]);
deep(k, oldVal===null?curVal:oldVal, i);
}
}
// prerequisites.forEach(([j,k]) => {
// if (j === curVal) {
// allQueries.push([curPreVal, k]);
// deep(k);
// }
// });
}
}
// deep(curNextVal,index);
for (let n = numCourses-1; n >= 0; n--) {
deep(n,null,0);
}
// });
// console.log(allQueries);
return queries.map(([i, j]) => {
return allQueries.some((item) => {
return JSON.stringify(item) === JSON.stringify([i, j]);
});
});
};
這個雖然暴力但是也是一般同學能第一時間想到的方案,將各個對映關係通過遞迴遍歷的方式全部收集,在將目標判斷資料進行比較
floyed演算法
巧妙利用最短路徑弗洛伊德演算法來解決
var checkIfPrerequisite = function(numCourses, prerequisites, queries) {
if (!prerequisites.length) {
return queries.map(() => {
return false;
});
}
// 思路二: floyed演算法
let dp = [];
for (let i = 0; i < numCourses; i++) {
dp[i] = new Array(numCourses).fill(false);
}
// console.log(dp);
prerequisites.forEach(([i, j]) => (dp[i][j] = true));
// console.log(dp);
for (let k = 0; k < numCourses; k++) {
for (let i = 0; i < numCourses; i++) {
for (let j = 0; j < numCourses; j++) {
if((dp[i][k] && dp[k][j])){
// console.log(k, i, j);
}
dp[i][j] = dp[i][j] || (dp[i][k] && dp[k][j]);
}
}
}
return queries.map(([i, j]) => dp[i][j]);
};
測試用例:
// console.log(
// checkIfPrerequisite(
// 5,
// [
// [3, 4],
// [0, 1],
// [1, 2],
// [2, 3],
// ],
// [
// [0, 4],
// [4, 0],
// [1, 3],
// [3, 0],
// ]
// )
// );
未完待續~
一直在路上的程式設計師ymc!