JS:三大算法day4

回溯、贪心、动态规划的思路是通过大量做题培养的,不是说几天就能掌握,而且题目不会告诉你使用哪个算法。坚持做题。。。

贪心算法

下面三题在区间问题中已做过,但没印象。所以可以当作新题了。

452. 用最少数量的箭引爆气球(复习)

上次思路是:用最少数量n来引爆区间==最多n个无重叠区间
随想录的思路是:尽量找越多的重叠区间,但重叠区间的右边以最小的为边界

var findMinArrowShots = function(points) {
    points.sort((a, b) => a[0]-b[0])
    var need = 1, minR = points[0][1]
    for(let i=1; i<points.length; i++) {
        if(points[i][0] > minR) {
            need++
            minR = points[i][1]
        } else {
            minR = Math.min(minR, points[i][1])
        }
    }
    return need
};

435. 无重叠区间 (复习)

需要移除区间的最小数量(==最多无重叠的区间),使剩余区间互不重叠 .
从贪心算法出发,右边界排序之后,局部最优:优先选右边界小的区间,所以从左向右遍历,留给下一个区间的空间大一些,从而尽量避免交叉。全局最优:选取最多的非交叉区间。

原来跟经典题——听讲座一样

56. 合并区间 (复习)

只考虑重叠和不相交两种情况

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

var merge = function(intervals) {
    intervals.sort((a, b) => a[0] - b[0])
    var st = intervals[0][0], en = intervals[0][1], ans = []
    for(let i=1; i<intervals.length; i++) {
        // 重叠
        if(intervals[i][0] <= en) {
            en = Math.max(intervals[i][1], en)
        } else if(intervals[i][0] > en) { // 不相交
            ans.push([st, en])
            st = intervals[i][0]
            en = intervals[i][1]
        }
    }
    ans.push([st, en])
    return ans;
};

763.划分字母区间 (中等)

字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。

输入:S = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca", "defegde", "hijhklij"。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 的划分是错误的,因为划分的片段数较少。

跟我想法一样,先用字典存下字符最远的位置。
但差一步:每次的right要保留最远的。

var partitionLabels = function(s) {
    var dic = {}
    for(let i=0; i<s.length; i++) {
        dic[s[i]] = i
    }
    var res = [], left = 0, right = 0
    for(let i=0; i<s.length; i++) {
        if(right < dic[s[i]]) {
            right = Math.max(right, dic[s[i]])
        } else if(right == i) {
            res.push(right-left+1)
            left = i + 1
            right = left
        }
    }
    return res
};

134. 加油站(中等)

在这里插入图片描述

那么局部最优:当前累加rest[j]的和curSum一旦小于0,起始位置至少要是j+1,因为从j开始一定不行。全局最优:找到可以跑一圈的起始位置。

思想是,第一步肯定是有贡献(>0)才作为起始位置。后面遇到走不下去了,就重新定义第一步。

 var canCompleteCircuit = function(gas, cost) {
    var curSum = 0, totalSum = 0, start = 0
    for(let i=0; i<gas.length; i++) {
        curSum += gas[i] - cost[i]
        totalSum += gas[i] - cost[i]
        if(curSum < 0) {
            start = i + 1
            curSum = 0
        }
    }
    if(totalSum < 0) return -1
    return start
};

而我的想法将差值数组从尾部累加,那么最大值,肯定是到达数组末尾是剩油最多的,如果有结果,那不就是最优的出发点。

135. 分发糖果(困难)fail

在这里插入图片描述

输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
     第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。

思路:
在这里插入图片描述
在这里插入图片描述

var candy = function(ratings) {
    var res = new Array(ratings.length).fill(1)
    // 只比左边节点
    for(let i=1; i<ratings.length; i++) {
        if(ratings[i] > ratings[i-1]) {
            res[i] = res[i-1] + 1
        }
    }
    // 在只比左边的基础上,比较右边节点
    for(let i=ratings.length-2; i>=0; i--) {
        if(ratings[i] > ratings[i+1]) {
            res[i] = Math.max(res[i], res[i+1] + 1)
        }
    }
    return res.reduce((pre, cur) => {
        pre += cur
        return pre
    }, 0)
};

860. 柠檬水找零 (简单)

在这里插入图片描述
我漏了:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5

贪心:因为美元10只能给账单20找零,而美元5可以给账单10和账单20找零,美元5更万能!

考察逻辑

var lemonadeChange = function(bills) {
    var five = 0, ten = 0
    for(let i=0; i<bills.length; i++) {
        let bill = bills[i]
        if(bill == 5) {
            five++
        }else if(bill == 10) {
            if(five > 0) {
                five--
                ten++
            } else {
                return false
            }
        }else{
            if(ten > 0 && five > 0) {
                ten--
                five--
            }else if(five > 2) {
                five -= 3
            }else {
                return false
            }
        }
    }
    return true
};

406. 根据身高重建队列 (中等)fail

在这里插入图片描述
如果两个维度一起考虑一定会顾此失彼。
在这里插入图片描述
在这里插入图片描述
按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。

所以在按照身高从大到小排序后:

局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性

全局最优:最后都做完插入操作,整个队列满足题目队列属性
在这里插入图片描述
还有一个细节,原来排序 [5,3],[5,2],那么插入[5,3]时忽略了[5,2]应该在前面。

即身高相同时,k小的排前面

var reconstructQueue = function(people) {
    let queue = []
    people.sort((a, b ) => {
        if(b[0] !== a[0]) {
            return b[0] - a[0]
        } else {
            return a[1] - b[1]
        }
    })
    for(let i = 0; i < people.length; i++) {
        queue.splice(people[i][1], 0, people[i])
    }
    return queue
};

回溯算法

131. 分割回文串(中等)

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。

"aaa"
[["a","a","a"],["a","aa"],["aa","a"],["aaa"]]
function judge(st, en, s) {
    for(let i=st, j=en; i<j; i++, j--) {
        if(s[i] !== s[j]) return false
    }
    return true
}
var partition = function(s) {
    var res = [], temp = []
    function backtrack(index) {
        if(index == s.length) {
            res.push([...temp])
            return
        }
        for(let end=index; end<s.length; end++) {
            if(!judge(index, end, s)) continue
            temp.push(s.substr(index, end - index + 1))
            backtrack(end + 1)
            temp.pop()
        }
    }
    backtrack(0)
    return res
};

417. 太平洋大西洋水流问题(中等)

无意间做的题目,但也用到之前的想法,从两个方向考虑,但使用的是深度遍历。

93. 复原 IP 地址(中等)

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。

也是分割问题,差别在于判断条件。一个是不满足则continue,本题是不满足则break。

var restoreIpAddresses = function(s) {
    var res = [], temp = []
    function backtrack(start) {
        if(temp.length > 4) return
        if(temp.length == 4 && start == s.length) {
            res.push(temp.join("."))
            return
        }
        for(let end = start; end < s.length; end++) {
            const str = s.substr(start, end - start + 1)
            if(str.length > 3 || +str > 255) break
            if(str.length > 1 && str[0] == 0) break
            temp.push(str)
            backtrack(end + 1)
            temp.pop()
        }
    }
    backtrack(0)
    return res
};

78. 子集 (中等)

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

90. 子集 II (中等)

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

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
在这里插入图片描述

那么关于回溯算法中的去重问题,在40.组合总和II 中已经详细讲解过了,和本题是一个套路。
后期要讲解的排列问题里去重也是这个套路,所以理解“树层去重”和“树枝去重”非常重要。

var subsetsWithDup = function(nums) {
    var res = [], temp = []
    nums.sort((a, b) => a - b )
    
    function backtrack(start) {
        res.push([...temp])
        // var lastVal = -11
        for(let i = start; i < nums.length; i++) {
            // if(lastVal === nums[i]) continue
            if(i > start && nums[i] === nums[i - 1]) continue
            temp.push(nums[i])
            lastVal = nums[i]
            backtrack(i + 1)
            temp.pop()
        }
    }
    backtrack(0)
    return res
};

491. 递增子序列 (中等)

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
在这里插入图片描述

var findSubsequences = function(nums) {
    var res = [], temp = []
    function backtrack(start) {
        if(temp.length > 1) res.push([...temp])
        var used = {}
        for(let i=start; i<nums.length; i++) {
            if(used[nums[i]] || (temp.length > 0 && nums[i] < temp[temp.length-1])) continue
            temp.push(nums[i])
            used[nums[i]] = true
            backtrack(i + 1)
            // used[nums[i]] = false
            temp.pop()
        }
    }
    backtrack(0)
    return res
};

332. 重新安排行程 (困難)

难点:
使用什么数据结构,对谁遍历。

如果存在多种有效的行程,请你按字典排序返回最小的行程组合。

如果在解题的过程中没有对集合元素处理好,就会死循环。

var findItinerary = function(tickets) {
    let map = {}
    for(const val of tickets) {
        const [from, to] = val
        if(!map[from]) {
            map[from] = []
        }
        map[from].push(to)
    }
    for(const city in map) {
        map[city].sort()
    }
    let temp = ['JFK'], res = [], flag = true
    function backtrack(lastCity) {
        if(temp.length == tickets.length + 1 && flag) {
            res.push([...temp])
            flag = !flag
        }
        if(!map[lastCity] || map[lastCity].length == 0) {
            return
        }
        for(let i = 0; i < map[lastCity].length; i++) {
            let city = map[lastCity][i]
            map[lastCity].splice(i, 1)
            temp.push(city)
            if(flag) {
                backtrack(city)
            }
            temp.pop()
            map[lastCity].splice(i, 0, city)
        }
    }
    backtrack('JFK')
    return res[0]
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值