- 3. Longest-Substring-Without-Repeating-Characters
- 4. Median-of-Two-Sorted-Arrays
- 5. longest-palindromic-substring
- 6. ZigZag-Conversion
- 11. Container-With-Most-Water.md
- 12. Integer-to-Roman.md
- 13. Roman-to-Integer.md
- 134. Gas-Station.md
- 14. Longest-Common-Prefix.md
- 15. 3sum.md
- 16. 3Sum-Closest.md
- 17. Letter-Combinations-of-a-Phone-Number.md
- 19. Remove-Nth-Node-From-End-of-List.md
- 20. Valid-Parentheses.md
- 21. Merge-Two-Sorted-Lists.md
- 22. Generate-Parentheses.md
- 6. ZigZag-Conversion.md
- 7. Reverse-Integer.md
- 9. Palindrome-Number.md
3. Longest-Substring-Without-Repeating-Characters
说明
最基本的思路是,遍历字符串,计算以每个字符开头的无重复子串长度,但是这样做的复杂度会很大,无法通过最后一个用例(这个题目的最后一个用例是用来测试算法的时间复杂度的)。
线性复杂度的算法思想描述如下:
遍历字符串,每次计算以当前字符结尾的最长不重复子串的length,遍历完成时,就得到了整个字符串的最长不重复子串。遍历时,将每个遇到的字符及其index以key:value形式存入一个hash中,我们每遇到一个字符,从hash中取寻找,判断之前是否遇见过,如果遇见过,那以当前字符结尾的最长不重复子串的开头字符的index的最小值就是最近一次遇到的这个字符的index。那么是不是说,最近一次遇到的这个字符的index就是开头了呢?不一定,因为我们要保证两个这两个相同的字符之间没有重复字符。如何保证这一点呢?这需要我们维护一个 名叫 start 的变量,每次发现一个字符之前遇到过,那就将start更新为 之前遇到的字符index。
代码
/*** @param {string} s* @return {number}*/var lengthOfLongestSubstring = function(s) {var hash = { };var start = -1;var max = 0;var dis = 0;for (var i = 0; i < s.length; i++) {if (hash[s[i]] === undefined) {dis = i - start;}else {if (hash[s[i]] > start) {dis = i - hash[s[i]];start = hash[s[i]];}else {dis = i - start;}}hash[s[i]] = i;max = (dis > max) ? dis : max;}return max;};
4. Median-of-Two-Sorted-Arrays
说明:
先将两个数组归并,然后找出中间值
代码:
/*** @param {number[]} nums1* @param {number[]} nums2* @return {number}*/var findMedianSortedArrays = function(nums1, nums2) {var arr = [];var i = 0;var j = 0;var k = 0;var mid;while (i < nums1.length && j < nums2.length) {if (nums1[i] < nums2[j]) {arr.push(nums1[i++]);}else {arr.push(nums2[j++]);}}if (i < nums1.length) {for (k = i; k < nums1.length; k++) {arr.push(nums1[k]);}}if (j < nums2.length) {for (k = j; k < nums2.length; k++) {arr.push(nums2[k]);}}if (arr.length % 2 === 0) {mid = (arr.length / 2) - 1;return (arr[mid] + arr[mid + 1]) / 2;}else {mid = (arr.length + 1) / 2 - 1;return arr[mid];}};
5. longest-palindromic-substring
说明
最长回文串,考虑到长度为奇数的回文串和长度为偶数的回文串。
代码
var longestPalindrome = function(s) {var max = 0;var mark = {i: 0,j: 0};var result = '';var getOddLength = function (s, index) {var i = index;var j = index;while (i >= 0 && j < s.length) {if (s[i] === s[j]) {i--;j++;}else {break;}}return j - i + 1;};var getEvenLength = function (s, index) {if (index >= s.length ) {return 0;}var i = index;var j = index + 1;while (i >= 0 && j < s.length) {if (s[i] === s[j]) {i--;j++;}else {break;}}return j - i + 1;};var getResult = function (mark, s) {var i = mark.i;var j = mark.j;var str = '';while (i >= 0 && j < s.length) {if (s[i] === s[j]) {str = s[i] + str;if (i !== j) {str = str + s[j];}i--;j++;}else {break;}}return str;};for (var i = 0; i < s.length; i++) {var l1 = getOddLength(s, i);var l2 = getEvenLength(s, i);if (l1 > max) {max = l1;mark.i = i;mark.j = i;}if (l2 > max) {max = l2;mark.i = i;mark.j = i + 1;}}return getResult(mark, s);};
6. ZigZag-Conversion
说明:
首先要弄清zigzag模式的概念,在博客http://blog.csdn.net/zhouworld16/article/details/14121477中,有很清晰的介绍。
根据题意,可以知道,若要得到正确的结果,只需要将输入的字符串按照zigzag模式分配给给定数目(numRows)的数组,然后再讲每个字符串拼接起来即可。
代码:
/*** @param {string} s* @param {number} numRows* @return {string}*/var convert = function(s, numRows) {var result = '';var arr = [ ];var down = true;var index = 0;var row = 0;var i;// 创建长度为numRows的数组for (i = 0; i < numRows; i++) {arr.push('');}// 为数组分配字符while (index < s.length) {arr[row] += s[index];if (down) {if (row < numRows - 1) {row++;if (row === numRows - 1) {down = false;}}}else {if (row > 0) {row--;if (row === 0) {down = true;}}}index ++;}// 将数组中的字符串拼接得到结果for (i = 0; i < arr.length; i++) {result += arr[i];}return result;};
11. Container-With-Most-Water.md
说明:
题目是:给定n个非负整数 a1, a2..., ai..., an,表示在点(i, ai)处的点。现在根据这个整数序列画出n条线段,每个线段的两个端点分别为(i, 0)和(i, ai)。要求是从这n条线段中找出两条,使得这两条直线与x轴围成的容器所能容纳的水最多。题目说是找出两条线段围成的容器中容纳水最多的那个,其实就是找出两条线段,使得(这两条线段之间的水平距离) * (两条线段中较短的那条) 值最大。很容易想到的思路就是暴力穷举法,遍历每个点,计算 这个点对应的线段和其他所有线段围成的容器容量 中最大的值,记为maxi,求出最大的maxi,就是题目要求的结果。代码如下:
暴力穷举代码:
/*** @param {number[]} height* @return {number}*/var maxArea = function (height) {// 计算两点围成的容器的容量var getArea = function (i, j) {var h = height[i] < height[j] ? height[i] : height[j];return h * (j - i);};var max = 0;// 遍历求解for (var i = 0; i < height.length; i++) {for (var j = i + 1; j < height.length; j ++) {var area = getArea(i, j, height[i], height[j])if (area > max) {max = area;}}}return max;};
很明显这个的复杂度是n^2。这个代码不能通过leetcode测试时间性能的测试用例。那么我们需要优化一下。如果我们希望能使一个算法的时间性能提升上去,即减少执行时间,可以从两个方面入手,第一是使用问题中的一些规则,减少算法的操作;第二是我们在算法过程中记录一些信息,在后续的执行过程中直接使用信息而不用再次计算。第二种思路的典型代表就是动态规划的思想。我们这里使用第一种思路,规则就是:如果在点j处的线段是从点到点j之间的线段中最长的,那么,点i到点j之间的所有可能的容器中容量最大的就是点i处的线段和点j处的线段所围成的容器(原因是既然j处的线段是从i到j之间最长的,那么i到j之间的线段和i围成的容器的长和宽都不可能比j更大,因此j处的线段和i所围成容器一定是容量最大的)。根据这个规则,我们要记录两个信息:一个是正序 从下标为0到下标为i之间的线段中最长线段的下标,另一个是逆序 从下标为height.length -1 到下标为i之间的线段中最长的线段的下标。用这两个信息,当遍历到i时,如果i处的线段不是从0到i之间最大的,就可以不作任何操作(因为根据上面的规则,这时 i和其后任何线段围成的容器 都不会比 i之前那个比它长的线段和任何线段围成的容器的容量更大);而从i到其后最大的线段之间的容器容量也不必计算(因为它们和i围成的容器容量不会比i后最大的线段更大)。代码如下:
var maxArea = function (height) {// 计算两点围成的容器的容量var getArea = function (i, j) {var h = height[i] < height[j] ? height[i] : height[j];return h * (j - i);};var max;var temp;// 记录从0到i的最大height的下标var preMax = [ ];// 记录从heiht.length-1到i的最大height的下标var postMax = [ ];var i, j;// temp在这里保存从0到i的最大heighttemp = 0;for (i = 0; i < height.length; i++) {if (height[i] > temp) {temp = height[i];preMax.push(i);}else {preMax.push(preMax[i - 1]);}}// temp在这里保存从heiht.length-1的最大heighttemp = 0;for (i = height.length - 1; i >= 0; i--) {if (height[i] > temp) {temp = height[i];postMax.unshift(i);}else {postMax.unshift(postMax[0]);}}max = 0;// 遍历height数组,求容量最大值for (i = 0; i < height.length; i++) {if (preMax[i] === i) {var area = getArea(i, postMax[i]);if (max < area) {max = area;}for (j = postMax[i] + 1; j < height.length; j++) {area = getArea(i, j);if (max < area) {max = area;}}}}return max;};
这样就可以accept了。
12. Integer-to-Roman.md
说明:
该题目要求是将一个整数转换为一个罗马数字,所以首先要搞清楚罗马数字的规则。
罗马数字的规则:
罗马数字和阿拉伯数字一样,都是十进制的,所以我们只要把每一位的数字拼凑到一起就可以了。但是有一点需要注意的是,罗马数字不同于阿拉伯数字,它的每一位数字表示方法不同,比如,对于阿拉伯数字,五用 ‘5’来表示,而五十只需要把 '5'放在十位就行了:‘50’,因为有0占位,因此不会有任何歧义。但是罗马数字没有占位机制,因此各个位的表示方法不同,如五用罗马数字表示为 ‘V’,五十则为 ‘X’。虽然罗马数字每一个位对应的数字符号不同,但是表示的方法都是一样的,都是通过表示‘1’的符号和表示‘5’的符号组合而成。表示方法如下:假设某一位表示‘1’的符号为A,表示‘5’的符号为B,则 0,1,2,3,4,5,6,7,8,9分别为 ‘’(空字符),A, AA, AAA, AB(可以这么理解,A位于B左侧相当于B-A,A位于B右侧相当于B+A), B, BA, BAA, BAAA, AC(C是更高一位的‘1’,相当于10 - 1)。根据上面的叙述,我们只需要知道每一位表示 ‘1’和 ‘5’的符号,就可以按照上述规则拼凑起罗马数字了。值得注意的是,题目中说明输入整数从1 ~ 3999 ,因此只需要知道个位到千位的表示符号即可。
代码:
/*** @param {number} num* @return {string}*/var intToRoman = function(num) {var result = '';var temp;var list = [{one: 'I',five: 'V'},{one: 'X',five: 'L'},{one: 'C',five: 'D'},{one: 'M'}];var map = function (integer, cur, next) {var one = cur.one;var five = cur.five;var ten = next && next.one;switch (integer) {case 0:return '';case 1:return one;case 2:return one + one;case 3:return one + one + one;case 4:return one + five;case 5:return five;case 6:return five + one;case 7:return five + one + one;case 8:return five + one + one + one;case 9:return one + ten;}};while (num) {temp = num % 10;result = map(temp, list[0], list[1]) + result;list.shift();num = Math.floor(num / 10);}return result;};
13. Roman-to-Integer.md
说明:
题目要求将罗马数字转为阿拉伯数字。思路是从个位开始,匹配各个位的数字,一直到最高位为止。需要注意两点,第一是要移除已经匹配完的数字,防止在匹配更高位数字时产生歧义。第二是在匹配的时候,为防止歧义,应遵循一定顺序,比如应先匹配4,6,7,8然后再匹配5。
代码:
/*** @param {string} s* @return {number}*/var romanToInt = function(s) {var num = 0;var list = [{grade: 1,one: 'I',five: 'V'},{grade: 10,one: 'X',five: 'L'},{grade: 100,one: 'C',five: 'D'},{grade: 1000,one: 'M'}];var map = function (cur, next) {var result = 0;var one = cur.one;var five = cur.five;var ten = next && next.one;var nine = ten ? (one + ten) : 'doomliu';var fiveIndex = s.indexOf(five);var oneIndex = s.indexOf(one);var nineIndex = s.indexOf(nine);if (nineIndex !== -1) {s = s.slice(0, nineIndex);result = 9;}else if (fiveIndex !== -1) {var preIndex = postIndex = fiveIndex;while (preIndex >= 0 && (s[--preIndex] === one)) { }while (postIndex <= s.length - 1 && (s[++postIndex] === one)) { }s = s.slice(0, preIndex + 1);result = 5 + ((postIndex - 1) + (preIndex + 1) - 2 * fiveIndex);}else if (oneIndex !== -1) {var postIndex = oneIndex;while (postIndex <= s.length - 1 && s[++postIndex] === one) { }s = s.slice(0, oneIndex);result = (postIndex - 1) - oneIndex + 1;}result *= cur.grade;return result;};while (list.length) {num += map(list[0], list[1]);list.shift();}return num;};
134. Gas-Station.md
代码
/*** @param {number[]} gas* @param {number[]} cost* @return {number}*/var canCompleteCircuit = function(gas, cost) {var arr = [];var curSum = 0;var positiveSum = 0;var positiveIndex = 0;var index = gas.length - 1;gas.forEach((item, index) => {arr.push(item - cost[index])});while (index >= 0) {if (arr[index] < 0) {curSum = arr[index--];while (curSum < 0 && index >= 0) {curSum += arr[index--];}if (index < 0) {if (curSum + positiveSum >= 0) {return positiveIndex}else {return -1}}else {positiveSum += curSum;positiveIndex = index + 1;curSum = 0;}}else {while (arr[index] >= 0 && index >= 0) {positiveSum += arr[index];positiveIndex = index--;}if (index < 0) {return 0}}}return -1};
14. Longest-Common-Prefix.md
说明:
题目要求找出一个字符串数组中的所有字符串的最长前缀。
代码:
/*** @param {string[]} strs* @return {string}*/var longestCommonPrefix = function (strs) {var result = '';var breakFlag = false;var curStr = '';var i = j = 0;if (!strs.length) {return result;}while (true) {if (i > strs[0].length - 1) {break;}curStr = strs[0][i];for (j = 0; j < strs.length; j++) {if (strs[j][i] !== curStr || i > strs[j].length - 1) {breakFlag = true;break;}else if (j === strs.length - 1) {result += strs[j][i];}}if (breakFlag) {break;}i++;}return result;};
15. 3sum.md
说明
题目是,给定一个数组(nums),返回一个数组(假定为result),result的每个元素都是一个数组(假定为ele),ele长度为3,其中的3个元素不重复地取自nums,并且这3个元素之和为0。result中的各数组要保证3元素组合不重复。首先最容易想到的方法是暴力遍历,用一个3重循环求取结果,再去重即可。这样复杂度为n^3。可以从多个角度进行优化第一,先把输入参数nums升序排序,这样可以在遍历的过程中判断两数之和是否大于0,如果大于0可以不用再向后遍历,这就减少了一定的计算量;第二,可以给nums去重,保证每个元素最多只有3个重复;第三,可以先把nums中每个元素存到一个hash里,key就是这个元素的值,value是这个元素最后出现的下标,遍历的时候,只需要两重遍历即可;
代码
/*** @param {number[]} nums* @return {number[][]}*/var threeSum = function(nums) {var resultHash = {};var numsHash = {};var result = [];var newNums = [];var newNumsHash = {};// nums去重nums.forEach(function (item, index) {if (newNumsHash[item] !== 3) {newNums.push(item);if (newNumsHash[item]) {newNumsHash[item]++;}else {newNumsHash[item] = 1;}}});// 排序var list = newNums.sort(function (a, b) {return a - b;});// 将nums转成hashfor (var i = 0; i < list.length; i++) {numsHash[list[i]] = i + 1;}// 两重遍历确定两个数,第三个数在numsHash里找for (var j = 0; j < list.length; j++) {for (var k = j + 1; k < list.length; k++) {var target = -(list[j] + list[k]);// 因为数组是升序排好序的,所以如果两数之和大于0,再加上后面的肯定不为0if (target < 0) {break;}if (numsHash[target]&& (numsHash[target] - 1) !== j&& (numsHash[target] - 1) !== k) {var arr = [list[j], list[k], target];var resultHashKey = arr.sort().join('_');if (!resultHash[resultHashKey]) {result.push(arr);resultHash[resultHashKey] = true;}}}}return result;};
16. 3Sum-Closest.md
说明
题目是,给定一个数组,和一个目标值,找出数组中的3个数,这3个数之和离目标值最近。可以直接想出的方法就是三重循环遍历。为了减少计算,需要先对数组进行排序,然后根据遍历中当前3个元素之和与目标值的关系判定是否需要继续向后遍历。
代码
/*** @param {number[]} nums* @param {number} target* @return {number}*/var threeSumClosest = function(nums, target) {var list = nums.sort(function (a, b) {return a - b;});var distance = Math.abs((list[0] + list[1] + list[2]) - target);var tempSum;for (var i = 0; i < list.length; i++) {for (var j = i + 1; j < list.length; j++) {for (var k = j + 1; k < list.length; k++) {if (Math.abs(list[i] + list[j] + list[k] - target) > distance) {if ((list[i] + list[j] + list[k] - target) > 0) {break;}}else {distance = Math.abs(list[i] + list[j] + list[k] - target);tempSum = list[i] + list[j] + list[k];}}}}return tempSum;};
17. Letter-Combinations-of-a-Phone-Number.md
说明
当我们使用手机的9键键盘时,每个按键对应多个字母,我们按下几个键后,对应多种可能的字母组合。题目要求根据输入的数字求出所有可能的字母组合。这个题就是简单的递归题目,和字符全排列类似。
代码
/*** @param {string} digits* @return {string[]}*/var letterCombinations = function(digits) {const map = {'2': ['a', 'b', 'c'],'3': ['d', 'e', 'f'],'4': ['g', 'h', 'i'],'5': ['j', 'k', 'l'],'6': ['m', 'n', 'o'],'7': ['p', 'q', 'r', 's'],'8': ['t', 'u', 'v'],'9': ['w', 'x', 'y', 'z']};var result = [];const it = (prefix, seq, result) => {if (seq.length === 0) {return;}else if (seq.length === 1) {map[seq[0]].forEach((item, index) => {result.push(prefix + item);});return;}else {map[seq[0]].forEach((item, index) => {it(prefix + item, seq.slice(1), result);});}}it('', digits, result);return result;};
19. Remove-Nth-Node-From-End-of-List.md
说明
题目要求删除链表倒数第n个节点。这是个典型的双指针问题,先让前指针走n步,然后两个指针一起走,直到前指针走到链表末尾,最后删掉后指针指向的节点即可。值得注意的是,这里的头指针不是一个指向第一个节点的节点,它就是第一个节点。
代码
/*** Definition for singly-linked list.* function ListNode(val) {* this.val = val;* this.next = null;* }*//*** @param {ListNode} head* @param {number} n* @return {ListNode}*/var removeNthFromEnd = function(head, n) {var h = new ListNode(0);h.next = head;var i = h, j = h;for (var k = 0; k < n; k++) {j = j.next;}while (j.next) {i = i.next;j = j.next;}i.next = i.next.next;return h.next;};
20. Valid-Parentheses.md
说明
题目要求判断一个括号序列是否合法。这是一个典型的栈问题。
代码
/*** @param {string} s* @return {boolean}*/var isValid = function(s) {var stack = [];var match = {')': '(',']': '[','}': '{',};for (var i = 0; i < s.length; i++) {if (stack.length > 0 && match[s[i]] === stack[stack.length - 1]) {stack.pop();}else {stack.push(s[i]);}}if (stack.length === 0) {return true;}return false};
21. Merge-Two-Sorted-Lists.md
说明
合并两个列表,这里list使用的是链表结构
代码
// Definition for singly-linked list.function ListNode(val) {this.val = val;this.next = null;}/*** @param {ListNode} l1* @param {ListNode} l2* @return {ListNode}*/var mergeTwoLists = function(l1, l2) {var i = l1;var j = l2;var h1 = h2 = new ListNode();while (i && j) {if (i.val < j.val) {h2.next = i;h2 = h2.next;i = i.next;}else {h2.next = j;h2 = h2.next;j = j.next;}}if (i) {while (i) {h2.next = i;h2 = h2.next;i = i.next;}}if (j) {while (j) {h2.next = j;h2 = h2.next;j = j.next;}}return h1.next;};
22. Generate-Parentheses.md
说明:
题目要求找出n个括号所能组成的所有合法括号序列,所谓合法括号序列,指的是从左到右遍历序列,任意位置都满足,左括号的个数大于等于右括号的个数。
代码:
/*** @param {number} n* @return {string[]}*/var generateParenthesis = function(n) {var stack = [];var result = [];var curLeft = 0;var curRight = 0;function getEnd(n) {var result = ''for (var i = 0; i < n; i++) {result += '()';}return result;}var end = getEnd(n);function reachEnd() {return stack.join('') == end;}function init() {for (var i = 0; i < n; i++) {stack.push('(')}for (var i = 0; i < n; i++) {stack.push(')')}result.push(stack.join(''));}init();while (!reachEnd()) {// 出栈while (true) {var ele = stack.pop();if (ele == '(') {curLeft++if (curLeft < curRight) {break;}}else {curRight++}}// 入栈while (curRight) {stack.push(')');curRight--;while (curLeft) {stack.push('(')curLeft--}while (curRight) {stack.push(')')curRight--}}result.push(stack.join(''))}return result};
6. ZigZag-Conversion.md
说明:
首先要弄清zigzag模式的概念,在博客http://blog.csdn.net/zhouworld16/article/details/14121477中,有很清晰的介绍。
根据题意,可以知道,若要得到正确的结果,只需要将输入的字符串按照zigzag模式分配给给定数目(numRows)的数组,然后再讲每个字符串拼接起来即可。
代码:
/*** @param {string} s* @param {number} numRows* @return {string}*/var convert = function(s, numRows) {var result = '';var arr = [ ];var down = true;var index = 0;var row = 0;var i;// 创建长度为numRows的数组for (i = 0; i < numRows; i++) {arr.push('');}// 为数组分配字符while (index < s.length) {arr[row] += s[index];if (down) {if (row < numRows - 1) {row++;if (row === numRows - 1) {down = false;}}}else {if (row > 0) {row--;if (row === 0) {down = true;}}}index ++;}// 将数组中的字符串拼接得到结果for (i = 0; i < arr.length; i++) {result += arr[i];}return result;};
7. Reverse-Integer.md
说明:
32位有符号整数的范围就是-2^31 ~ 2^31。
该题目有个细节要注意,就是不仅输入的数32位溢出时要返回0。而且翻转后的数32位溢出时也要返回0。
代码:
/*** @param {number} x* @return {number}*/var reverse = function(x) {var result = 0;if (Math.abs(x) >= 2147483648) {result = 0;}else {if (x >= 0) {result = +(('' + x).split('').reverse().join(''));}else {result = -(('' + -x).split('').reverse().join(''))}}if (Math.abs(result) >= 2147483648) {result = 0;}return result;};
9. Palindrome-Number.md
说明
判断一个数字是否具有回文特性,不允许使用额外存储空间
思路是,先将改数转换为一个字符串,然后一次比较头尾两个字符是否相等,然后掐头去尾。如果一直相等直到只剩下0个字符或者1个字符,那么可以断定改数为回文数
代码
var isPalindrome = function(x) {x = '' + x;while (x !== '') {if (x[0] === x[x.length - 1] && x.length >= 2) {x = x.slice(1, x.length - 1).slice(0, x.length - 2);}else {break;}}if (x.length <= 1) {return true;}return false;};
