T11 盛最多水的容器
朴素解法
我们可以采用暴力枚举的朴素解法直接得出答案。
代码如下:
function maxArea(heights) {
let len = heights.length;
let ans = 0
for(let i = 0; i < len; i++) {
for(let j = i + 1; j < len; j++) {
let height = Math.min(heights[i], heights[j]);
let w = j - i;
ans = Math.max(ans, height * w);
}
}
return ans;
}
这种解法的缺陷也很明显,即枚举次数庞大,程序效率低下。
双指针+贪心
我们不妨先设双指针i
、j
,使之分别指向heights
数组的两端。现在让我们来分析一下,当指针向数组内侧移动时会发生什么。
我们知道,盛水的面积Area = w * h
,其中w = j - i
、h = min(heights[i], heights[j])
。因此当指针向内侧移动时,w
必然减小,因而如果我们希望Area
取值尽可能地大,那么就必须使得h
取值尽可能大。
下面我们来分析一下每一次指针移动时,h
的变化情况。为了分析的便利,我们不妨假设heights[i]
小于heights[j]
,即h = min(heights[i], heights[j]) = heights[i]
。
当i
向右移动,j
不变时:
- 当
heights[i + 1] < heights[i]
时,w
减小,h
减小,Area
减小- 当
heights[i + 1] = heights[i]
时,w
减小,h
不变,Area
减小- 当
heights[i + 1] > heights[i]
时,w
减小,h
变大,Area
变大
当i
不变,j
向左移动时:
- 当
heights[j - 1] < heights[j]
时
- 若
heights[j - 1] ≥ heights[i]
,w
减小,h
不变,Area
减小- 若
heights[j - 1] < heights[i]
,w
减小,h
减小,Area
减小
- 当
heights[j - 1] = heights[j]
时,w
减小,h
不变,Area
减小- 当
heights[j - 1] > heights[j]
时,w
减小,h
不变,Area
减小
综上所述,为了尽可能使Area
取得较大的值,我们应时刻尝试将指向高度较小的指针向内测移动。因为由上分析,除此之外我们别无更佳的选择,前述的便已经是最佳的贪心策略!
代码如下:
function maxArea(heights) {
let ans = 0
let i = 0;
let j = heights.length - 1;
while(i < j) {
let height = Math.min(heights[i], heights[j]);
let w = j - i;
ans = Math.max(ans, height * w);
if(heights[i] < heights[j]) {
i++;
} else {
j--;
}
}
return ans;
}
在本题中,经过对贪心策略的分析,我们极大简化了枚举的范围,将原本需要双重循环的算法压缩到了只需遍历数组至多一次即可得出结果,实现了程序效率的提升。
T20 有效的括号
错解释疑
这是一道简单的模拟题。
我们拿到题目的第一个想法或许是先获取一个左括号,然后向右检查检查是否在对应位置存在的右括号。这样的想法正确吗?
假如我们需要检测的字符串是类似于{[()]}
这般的,这样做当然没问题;但是假如像题目中给出的例子()[]{}
,这么做可就不行了!
栈+哈希表
栈是本题公认的正确解法。由于题目较为简单,下面直接给出代码:
function isValid(str) {
const hash = new Map( [[')', '('], [']', '['], ['}', '{']] );
const stack = [];
for(let i = 0; i < str.length; i++) {
let s = str[i];
if (s === '(' || s === '[' || s === '{') {
stack.push(s);
} else if(hash.get(s) === stack[stack.length - 1]) {
stack.pop();
} else {
return false;
}
}
return stack.length === 0;
}
拓展:栈+ASCII码
想必有强迫症的同学已经注意到了,我们在上面的代码中反复写了两遍(
、[
、{
,看着的确很不舒服,有没有避免这么做的办法?
OK,现在请你打开浏览器DevTools的Console面板,将下面的代码粘贴进去,看看会输出什么:
console.log( '}'.charCodeAt(0) - '{'.charCodeAt(0) );
console.log( ')'.charCodeAt(0) - '('.charCodeAt(0) );
console.log( ']'.charCodeAt(0) - '['.charCodeAt(0) );
尝试过的你一定惊喜地发现,两种对应括号之间的内码值不会超过2
,而又由于题目中已经明确说明字符串中只会出现这三类括号,所以我们可以放心地运用这个结论来改造我们的代码。
改造后的代码如下:
function isValid(str) {
const stack = [];
for(let i = 0; i < str.length; i++) {
let s = str[i];
if (s === '(' || s === '[' || s === '{') {
stack.push(s);
} else if(stack.length > 0) {
let delta = s.charCodeAt(0) - stack[stack.length - 1].charCodeAt(0);
if(delta > 0 && delta <= 2) {
stack.pop();
} else {
return false;
}
} else {
return false;
}
}
return stack.length === 0;
}
这样就不需要写两遍括号了吧,哈哈!
写在文末
我是来自在校学生编程兴趣小组江南游戏开发社的PAK向日葵,我们目前正在致力于开发自研的非营利性网页端同人游戏《植物大战僵尸:旅行》,以锻炼我们的前端应用开发能力。
我们诚挚邀请您体验我们的这款优秀作品,如果您喜欢TA的话,欢迎向您的同事和朋友推荐。如果您有技术方面的问题希望与我们探讨,欢迎直接与我联系。您的支持是我们最大的动力!