一周力扣刷题笔记(11月13日)

这周由于各种杂事比较多一点,因此刷题主要集中于一些比较简单的题目来巩固基础。另外,这种主要学习了哈希表,也做了一些题目

反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
从上周的经验来看,这周需要多次操作链表next指向的算法,可以使用一个数组对所有节点进行分割保存,再统一重新连接,因此最开始的做法如下:

var reverseList = function(head) {
    let res = head;
    let arr = [];
    // 用一个数组保存所有节点
    while (res) {
        let temp = res;
        res = res.next;
        temp.next = null;
        arr.push(temp);
    }
    let headnode = new ListNode(0);
    res = headnode;
    // 倒序输出并连接
    for (let i = arr.length - 1; i >= 0; i--) {
        res.next = arr[i];
        res = res.next;
    }
    return headnode.next;
};

之后发现,对于这一道题,我们其实只需要交换next的指向即可,让每一个next的指向进行交换,最后不断地迭代,知道遍历整个链表就可以了

var reverseList = function(head) {
    if (!head || !head.next) return head;
    // 初始化指针
    let pre = null;
    let temp = null;
    // 当前指针指向头节点
    let cur = head;
    // 遍历,每次让两个节点的next指向交换
    while (cur) {
        // 由于要交换,保存当前节点后面的所有节点
        temp = cur.next;
        // 让当前节点指向前一个节点
        cur.next = pre;
        // 迭代,让两个指针向前移动
        pre = cur;
        cur = temp;
    }
    return pre;
};

合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。

第一次的思路是,先将整个数组排序,如果两个数组有重复区间,那么将他们连接后并排序,跟直接将两个数组连接的值(这里要注意是值相等,因为引用一定不相等)不相等,说明两个数组有重复区间(因为已经经过排序,整个大数组应该是递增的),这时我们将这两个数组进行合并就可以了

var merge = function(intervals) {
    // 先进行排序
    intervals.sort((a, b) => a[0] - b[0]);
    let res = []
    for (let i = 0; i < intervals.length; i += 1) {
        //先试着合并两个数组
        let init = intervals[i].concat(intervals[i + 1]);
        if (init[1] < init[2]) { res.push(intervals[i]); continue; }
        // 对合并后的数组进行去重,排序
        let arr = [...new Set(init)].sort((a, b) => { return a - b })
            // 将两个数组转化为字符串(防止拷贝问题)进行比较
        if (!(arr.join('') == init.join(""))) {
            res.push([Math.min(intervals[i][0], intervals[i + 1][0]), Math.max(intervals[i][1], intervals[i + 1][1])]);
            // 一次合并后要跳过已经合并的一项
            i++;
        } else {
            res.push(intervals[i])
        }
    }
    return res;
};

但这种算法的问题是,不能进行连续合并,如果出现连续多个元素需要合并,并,这种方式就显得无能为力了,因为每次合并后需要直接跳过下一项,就无法进行连续合并。同时,边界相同的边界条件处理起来也比较麻烦。
更合理的方式是,通过贪心算法进行处理,每次遇到符合重复条件的元素时,就合并元素,然后先不将合并后的元素添加进最后返回的数组,而是继续循环进行贪心查找,直到不符合条件再进行添加,这样就解决了不能连续查找的问题

var merge = function(intervals) {
    intervals.sort((a, b) => a[0] - b[0]);
    let res = []
    let prve = intervals[0];
    for (let i = 1; i < intervals.length; i++) {
        cur = intervals[i];
        // 符合条件会贪婪查找
        if (prve[1] >= cur[0]) {
            // 由于前面已经进行了排序,这里就没有必要比较最小值,prve[0]一定是最小值
            prve[1] = Math.max(prve[1], cur[1])
        } else {
            // 不符合条件就推入res中
            res.push(prve);
            prve = cur;
        }
    }
    // 如果最后不再贪婪,应该强制推入
    res.push(prve)
    return res;
};

回文数

给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。

第一个思路很简单,利用栈进行解决,将数字转化为字符串,先进栈再出栈,如果进栈和出栈的结果相同,就说明是一个回文数

var isPalindrome = function(x) {
    let stack = [];
    let str = [];
    x=x.toString();
    for(let i =0;i<x.length;i++){
        stack.push(x[i]);
    }
    console.log(stack)
    for(let j =stack.length;j>0;j--){
        str.push(stack.pop());
    }
    console.log(str);
    return str.join('')==x;
};

另一种思路的话就是双指针解法,每次比对双指针的指向是否相同,如果迭代结束前指向不相同就返回false

var str = new String(x) // 1.先将数字转换成字符串
var left = 0,
    right = str.length - 1; // 2.双指针, 当指针相交时证明为回文
while (left <= right) { // 3.双指针往中间遍历时, 只要判断到彼此位置不同就返回
    if (str[left] !== str[right]) return false;
    left++;
    right--;
}
return true;

检测大写字母

我们定义,在以下情况时,单词的大写用法是正确的:
全部字母都是大写,比如 “USA” 。
单词中所有字母都不是大写,比如 “leetcode” 。
如果单词不只含有一个字母,只有首字母大写, 比如 “Google” 。
给你一个字符串 word 。如果大写用法正确,返回 true ;否则,返回 false。

思路很简单,进行分治,如果首字母是大写的话,则后面的所有字母必须全部大写或全部小写,如果首字母小写的话,后面的字母必须全部小写

var detectCapitalUse = function(word) {
    let aft = word.substr(1, word.length - 1);
    let bef = word.substr(0, 1);
    // aft为空直接返回true
    if (!aft) { return true }
    // 当第一个字母为大写时,后面的只能为全大写或全小写
    return (bef === bef.toUpperCase()) ? (aft == aft.toUpperCase() || aft == aft.toLowerCase()) :
        // 当第一个为小写时,后面只能为全大写
        (aft == aft.toLowerCase())
};

矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。
我们首先要明确,n是数组参数matrix的长度,m是matrix[0]的长度,我们可以先进行第一次遍历,用两个数组作为哈希表保存每个元素为零的矩阵元素的行和列,然后再第二次循环遍历进行置零

var setZeroes = function(matrix) {
    let m = matrix.length;
    let n = matrix[0].length;
    let col = [];
    let row = [];
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (matrix[i][j] == 0) {
                // 保存列
                col.push(i);
                // 保存行
                row.push(j);
            }
        }
    }
    console.log(col, row)
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            // 如果行在某个单元格或列在某个单元格,将该元素置于0
            if ((col.indexOf(i) != -1) || (row.indexOf(j) != -1)) {
                matrix[i][j] = 0;
            }
        }
    }
    return matrix
};

这里可以稍微改进一下,因为行列会出现重复元素,但只会一次置零,因此我们可以通过Set代替数组作为哈希表进行去重,加快效率

两数相除

给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。
返回被除数 dividend 除以除数 divisor 得到的商。
整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2

其实大概意思就是要自己不使用除法运算符,实现一个除法。思路的话,如果逐个增加进行遍历,效率显然过低,因此我们可以直接采用倍增的方法处理,将除数不断翻倍,进行除操作的次数)也翻倍(也就是在这一层递归中,如果从除数位置开始相加直到满足条件所用的次数,由于除数翻倍,因此按原来方式处理需要的次数也应该翻倍),当除数过大后,超过被除数时,进行递归,让除数重置,以翻倍前的位置作为新的起始点进行递归,直到恰好满足条件,将每一层递归的次数进行求和,就是从0将除数加到被除数的大小(附近)的次数,也就是商
注意这里边界条件要重点关注一下

整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
/**
 * @param {number} dividend
 * @param {number} divisor
 * @return {number}
 */
var divide = function(dividend, divisor) {
    // 边界条件判断
    if (dividend == 0) { return 0 }
    if (divisor == 1) { return dividend }
    let flag = 1;
    if ((dividend > 0 && divisor < 0) || (dividend < 0 && divisor > 0)) {
        flag = -1;
    }
    dividend = dividend > 0 ? dividend : -dividend;
    divisor = divisor > 0 ? divisor : -divisor;
    let res = div(dividend, divisor) > 2147483647 ? 2147483647 : div(dividend, divisor)
    return flag == 1 ? res : -res;

    function div(a, b) {
        if (a < b) { return 0 }
        let tab = b;
        let count = 1;
        // 以倍增的形式进行递归,每次将除数翻倍,得到的次数翻倍,当不满足条件时用剩余区间重新递归执行
        while ((tab + tab) <= a) {
            count = count + count;
            tab = tab + tab;
        }
        return count + div(a - tab, b)
    }
};

罗马数字转数字

罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数

按要求进行转化,可以建表进行匹配,最简单的方法也就是在表中就提前预置所有情况

var romanToInt = function(s) {
    const map = {
        I: 1,
        IV: 4,
        V: 5,
        IX: 9,
        X: 10,
        XL: 40,
        L: 50,
        XC: 90,
        C: 100,
        CD: 400,
        D: 500,
        CM: 900,
        M: 1000
    };
    let ans = 0;
    for (let i = 0; i < s.length;) {
        if (i + 1 < s.length && map[s.substring(i, i + 2)]) {
            ans += map[s.substring(i, i + 2)];
            i += 2;
        } else {
            ans += map[s.substring(i, i + 1)];
            i++;
        }
    }
    return ans;
};

如果一定要使用题目给出的表的话,就要进行一个判断,如果前面的数小于后面的数,匹配两个字符进行取差;反之匹配两个字符进行取和
这里可以这样处理的原因是,罗马数字如果是从大往小进行排列的,而大数有专门的标记,如果小数位于大数前面,就一定是使用了取差的表示方式

var romanToInt = function(s) {
    const symbolValues = new Map();
    symbolValues.set('I', 1);
    symbolValues.set('V', 5);
    symbolValues.set('X', 10);
    symbolValues.set('L', 50);
    symbolValues.set('C', 100);
    symbolValues.set('D', 500);
    symbolValues.set('M', 1000);
    let ans = 0;
    const n = s.length;
    for (let i = 0; i < n; ++i) {
        const value = symbolValues.get(s[i]);
        if (i < n - 1 && value < symbolValues.get(s[i + 1])) {
            ans -= value;
        } else {
            ans += value;
        }
    }
    return ans;
};

字母异位词分组

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母都恰好只用一次。

这道题一般方法就是逐个遍历,有跟已知数组(二维)中的某项相匹配的就直接添加,没有的话就在数组中将自己添加进去用于后续比对

var groupAnagrams = function(strs) {
    let res = new Array(strs.length)
    res[0] = [strs[0]]
    for (let j = 0; j < strs.length; j++) {
        for (let i = 0; i < res.length; i++) {
            let flag = res[i] && res[i][0].split("").every((item, index) => {
                return strs[j].indexOf(item) != -1;
            })
            flag ? res[i].push(strs[j]) : (res[i + 1] = [strs[j]])
        }
    }
    return res
};

进阶玩法就是对将每个元素取副本排序并转化位字符串作为哈希表的键,如果键存在就往键对应的数组中添加元素;否则添加键并将自身作为第一个元素

var groupAnagrams = function(strs) {
    let h = {};
    for (str of strs) {
        let key = str.split("").sort().join("");
        h[key] ? h[key].push(str) : h[key] = [str];
    }
    return Object.values(h)
};

最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。

逐个基本就是自己实现一个栈,要能高效返回栈中的最小元素,那么最好的方式就是将查找元素的这个过程由放在push的时候,注意这里要在最小值被pop出去后重置最小值

var MinStack = function() {
    this.arr = [];
    this.min = null;
};

/** 
 * @param {number} val
 * @return {void}
 */
MinStack.prototype.push = function(val) {
    this.arr.push(val);
    if (this.min == null || this.min > val) {
        this.min = val;
    }
};

/**
 * @return {void}
 */
// 利用pop进行缓冲
MinStack.prototype.pop = function() {
        let temp = this.arr.pop();
        if (temp == this.min) {
            let cur = this.arr[0];
            for (let i = 0; i < this.arr.length; i++) {
                if (this.arr[i] < cur) {
                    cur = this.arr[i];
                }
            }
            this.min = cur;
        }
        return temp;
    }
    /**
     * @return {number}
     */
MinStack.prototype.top = function() {
    return this.arr[this.arr.length - 1]
};

/**
 * @return {number}
 */
MinStack.prototype.getMin = function() {
    return this.min;
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值