一周力扣刷题笔记(11-07)

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

这周刷题整体比较分散,各个方面都做了一部分

逆波兰表达式

根据 逆波兰表示法,求表达式的值。有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

这道题基本是看网课时见过的,无非是用JS的方式重新实现了一遍,由于题目本身已经将进栈顺序给出了,所以我们要做的只是根据每个字符的类型确定进栈活出栈,遇到数字直接进栈,遇到符号从栈中pop出两个数字,按给出的符号进行运算,将结果出栈。最后栈中剩下的数就是结果。这里要注意的有两点,第一点是对于减号和除号,由于栈先进后出的原则,应该是用后出栈的减/除先出栈的。另外,对于除法还要判断除数为0的情况

  var evalRPN = function(tokens) {
    let stk = [];
    for (let i = 0; i < tokens.length; i++) {
        console.log(stk)
        if (isNumber(tokens[i])) {
            stk.push(tokens[i]);
        } else {
            let num1 = stk.pop();
            let num2 = stk.pop();
            let sum = count(num1, num2, tokens[i]);
            stk.push(sum);
        }
    }
    return stk[0]
	};

	function isNumber(item) {		//判断是符号还是数字
    let arr = ["+", "-", "*", "/"];
    return !arr.includes(item);
	}

	function count(num1, num2, dash) {
    num1 = parseInt(num1);//注意数字要从字符类型转为number
    num2 = parseInt(num2)
    let sum;
    if (dash == "+") {
        sum = num2 + num1;
    } else if (dash == "-") {
        sum = num2 - num1;
    } else if (dash == "*") {
        sum = num2 * num1;
    } else if (dash == "/") {
        sum = num2 / num1 | 0; //注意这里要判断可能等于0
    }
    return sum
	}

这样定义多个函数虽然能够达到目的,但还是显得比较臃肿,更好的方式是通过Map将每个符号和对应的计算方法进行映射,同时,这样通过has方法也简化了确定字符类型的步骤

var evalRPN = function(tokens) {
const s = new Map([
    ["+", (a, b) => a * 1 + b * 1],
    ["-", (a, b) => b - a],
    ["*", (a, b) => b * a],
    ["/", (a, b) => (b / a) | 0]
]);
const stack = [];
for (const i of tokens) {
    if (!s.has(i)) {
        stack.push(i);
        continue;
    }
    stack.push(s.get(i)(stack.pop(), stack.pop()))
}
return stack.pop();
};

重排链表

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln-1 → Ln

请将其重新排列后变为:

L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …

不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

最开始对这道题的思路就是,先遍历整个链表,获取到链表的长度,然后第二轮遍历链表,将后面一半的数据依此进栈,最后进行重排,依此出栈,跟前半部分的链表进行连接重排

var reorderList = function(head) {
    let res = head;
    let bef = head;
    let length = 0;
    let stk = [];
    while (res.next != null) {
        res = res.next;
        length++ //确定链表长度
    }
    let index = length / 2 == 0 ? length / 2 : length / 2 + 1;
    let befindex = 0;
    while (bef.next != null) {
        if (befindex > index) {
            stk.push(bef)
        }
        bef = bef.next;
        befindex++;
    }
    let res2 = head;
    length / 2 != 0 ? index++ : null;
    for (let i = 0; i < index; i++) {
        console.log(res2)
        res2.next = stk[stk.length - 1];
        stk.pop();
        res2 = res2.next;
    }
    return res2
	};

这种方法的弊端显而易见,一方面多次遍历链表需要大量定义头指针,而且还要考虑链表连接的问题,因为如果没有及时消除重排后链表的next指向,很容易出现next连接成死循环的情况
改进的方法是用数组来保存所有节点,并清除所有的next,重排时只要用数组的方式进行顺序处理,并连接next即可

var reorderList = function(head) {
    let res = head;
    let arr = [];
    while (res != null) {
        let p = res.next;
        res.next = null; //注意用数组进行缓存时要先将链表分割开来,这里要注意保存next的值
        arr.push(res);
        res = p;
    }
    let bef = 0;
    let aft = arr.length - 1;
    // 这里可以采用[bef, aft] = [0, arr.length - 1]的写法
    while (bef < aft) {
        arr[bef].next = arr[aft];
        // 注意防止为偶数时中间的两个节点形成循环
        bef + 1 !== aft && (arr[aft].next = arr[bef + 1]);
        bef++;
        aft--;
    }
    return arr[0]
};

2的幂次方

给你一个整数 n,请你判断该整数是否是 2 的幂次方。如果是,返回 true ;否则,返回 false 。

如果存在一个整数 x 使得 n == 2x ,则认为 n 是 2 的幂次方。
最简单的递归问题,不会做的自行罚站

var isPowerOfTwo = function(n) {
    if (n == 0) { return false }
    if (n == 1) { return true }
    if (n % 2 == 1) { return false }
    return isPowerOfTwo(n / 2);
};

C语言版本:

bool isPowerOfTwo(int n){
    if (n==0){return false;}
    if(n==1){return true;}
    if(n%2!=0){return false;}
    return isPowerOfTwo(n/2);
}

Pow(x,n)

实现 pow(x, n) ,即计算 x 的 n 次幂函数。
最开始看这道题直接使用暴力查找,虽然能够算出结果,但在用例过大的时候就会超时。

var myPow = function(x, n) {
    if (n == 0) { return 1 }

    function pow(n) {
        if (n <= 0) { return x }
        n--;
        return pow(n) * x
    }
    let sum = pow(Math.abs(n) - 1);
    return n > 0 ? sum : 1 / sum;
}

起始仔细想想,这道题跟上面的题目本质上是一个类型,如果从小往大判断,起始很难去寻找到一个合适的结束循环的条件,只能用一个相对比较大的条件进行判断,导致做了很多不必要的计算,而就跟上一道题一样,正确的方法其实可以对n做处理,每次以倍数的形式进行增加,就可以大大减少计算的时间,就跟上面那道题每次除二进行递归相同,本质都是做了简化的算法,减少了不必要的计算

var myPow = function(x, n) {
    if (n == 0) { return 1 }
    if (n < 0) {
        return 1 / myPow(x, -n);
    } //剪枝
      //当无法二分时,先减一,再乘x,保持值相等的前提下继续二分
    if (n % 2 == 1) { return x * myPow(x, n - 1) }
    // 二分n,每次折半,增加效率
    return myPow(x * x, n / 2)
};

数字1的个数

给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。
第一时间想到的还是直接循环,将每个数字都转化成数组的形式,对1的数量进行检测,虽然行得通,但在用例比较大的时候直接报超时

var countDigitOne = function(n) {
    let sum = 0;
    for (let k = n; k > 0; k--) {
        let arr = k.toString().split("");
        for (let i = 0; i < arr.length; i++) {
            if (arr[i] == "1") {
                sum = sum + 1;
            }
        }
    }
    return sum;
};

网上的方法还算是比较多的,可惜大部分我都不大能看懂(确实菜),就放一个唯一我能理解的版本上去吧

// 找规律法,逐个位置寻找规律
// 归纳法,对于个位出现的1:(n / 10) * 1 + (n % 10) >= 1 ? 1 : 0;
// 对于十位出现的1:(n / 100) * 10 + if (k > 19) 10 else if (k < 10) 0 else k - 10 + 1;
// 对于百位出现的1:(n / 1000) * 100 + if (k > 199) 10 else if (k < 100) 0 else k - 100 + 1;
// 最终归纳出: (n / (i * 10)) * i + if (k > 2 * i - 1) i else if (k < i) 0 else k - i + 1, 其中k = n % (i * 10);
var countDigitOne = function(n) {
    let count = 0;

    for (let i = 1; i <= n; i *= 10) {
        let divide = i * 10;
        let p = Math.floor(n / divide),
            k = n % divide,
            rest = 0;

        count += p * i;
        rest = (k > (2 * i - 1)) ? i : ((k < i) ? 0 : k - i + 1);
        count += rest;
    }
    return count;
};

子集2

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
最开始的想法是定义一个空数组,用循环来表示子集的个数和开始下标,进行循环,每次用空数组收集遍历结果,并放入返回结果的数组,最后对遍历完后的值进行去重

var subsetsWithDup = function(nums) {
    let arr = [];
    let temp = [];
    for (let i = 0; i <= nums.length; i++) { //从0开始,遍历每一种长度的可能性
        for (let j = 0; j < nums.length; j++) { //在每一次从不同的下标开始扫描
            temp = [] //重置temp的值
            for (let k = j; k < i; k++) {
                temp.push(nums[k]); //每次从j开始扫描k个元素放入数组
            }
            arr.push(temp)
        }
    }
    // 去重,这里去重存在问题,两个数组直接比较==会比较引用而不是值,set也无法去重
    arr = arr.filter((item, index) => { return item.length && item.length != 0 })
    console.log(temp)
    arr = Array.from(new Set(arr))
    return arr;
};

但我忽略了一个严重的问题,就是数组是引用类型,这就导致了两个元素相同的元素,由于内存地址不相同,导致用===和set根本无法进行去重,当然,用逐个比对的方式也可以进行去重,但这样显然太过于复杂
这道题其实是一道回溯的题目,分析一下,我们首先将每个数字都加入一个数组,然后将之前没有查过的元素不断添加进去,增加这些数组的深度,每次增加前返回值,加入最后的结果,同时每次添加后进行回溯,让在每一层可以将剩余可以放入的数都加入进去,分别往深进行添加,最后就回返回所有可能的结果。而查找每一种可能性也确实是回溯比较经典的应用之一
这里的难点其实是要搞清楚为什么用这种回溯的方式不会产生重复元素

// 注意这里采用数层去重,通过去除连续的相同项来去掉数层的重复元素
// 通过for循环回溯,中间的元素无法回溯到前面的元素,从而去掉了不同回溯线的重复数组
var subsetsWithDup = function(nums) {
//结合下面相邻元素直接continue的条件,除去相同项
    let arr = nums.sort((a, b) => { return a - b })
    let temp = [];
    let res = [];

    function fun(startindex, arr) {
    //注意这里收集的是temp的一个浅拷贝, temp在一层遍历上仍要使用
        res.push(temp.slice(0));
        if (startindex > nums.length - 1) {
            return
        }
        for (let i = startindex; i < nums.length; i++) {
            if (i > startindex && nums[i] === nums[i - 1]) {
                continue
            }
            temp.push(nums[i]);
            fun(startindex + 1, arr);
            //进行回溯
            temp.pop();
        }
    }
    fun(0, arr);
    return res;
};

移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点
不多说,这道题做不对自行罚站

var removeElements = function(head, val) {
    let headnode = new ListNode(0);
    headnode.next = head;
    let res = headnode;
    while (true) {
        if (res.next == null) { break; }
        if (res.next.val == val) {
            res.next = res.next.next;
        } else {
            res = res.next;
        }
    }
    return headnode.next;
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值