本系列主要记录 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!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。