给定一个整数数组 nums
和一个目标值 target
,请你在该数组中找出和为目标值的那 两个
整数,并返回他们的数组下标
。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
for(int i = 0; i < nums.length; i++){
for(int j = i + 1; j < nums.length; j++){
if(nums[i] + nums[j] == target){
return new int[]{i, j};
}
}
}
return result;
}
}
结果:失败
原因:超出时间限制
思路:
使用哈希表,可以将寻找 target - x
的时间复杂度降低到从O(N)
降低到 O(1)
。
这样我们创建一个哈希表,对于每一个 x
,我们首先查询哈希表中是否存在 target - x
,然后将 x
插入到哈希表中,即可保证不会让 x
和自己匹配。
算法:
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hashtable = new HashMap<Integer, Integer>();
for (int i = 0; i < nums.length; ++i) {
if (hashtable.containsKey(target - nums[i])) {
return new int[]{hashtable.get(target - nums[i]), i};
}
hashtable.put(nums[i], i);
}
return new int[0];
}
}
java中hashmap容器实现查找O(1)时间复杂度的思考
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
示例 1:
输入: 123
输出: 321
示例 2:
输入: -123
输出: -321
示例 3:
输入: 120
输出: 21
注意:
假设我们的环境只能存储得下 32 位
的有符号整数,则其数值范围为 [-2147483648, 2147483647]
。请根据这个假设,如果反转后整数溢出那么就返回 0
int 转 String,反转,String 转 int。
太繁琐,放弃。
通过取模运算
class Solution {
public int reverse(int x) {
int result = 0;
while (x != 0) {
int temp = x % 10;
x /= 10;
result = result * 10 + temp;
}
return result;
}
}
结果:失败
原因:没有解决整数溢出
的问题(不知道怎么完美处理)
画解算法
后的代码class Solution {
public int reverse(int x) {
int result = 0;
while (x != 0) {
int temp = x % 10;
x /= 10;
if (result > Integer.MAX_VALUE / 10 || (result == Integer.MAX_VALUE / 10 && temp > 7))
return 0;
if (result < Integer.MIN_VALUE / 10 || (result == Integer.MIN_VALUE / 10 && temp < -8))
return 0;
result = result * 10 + temp;
}
return result;
}
}
思路:
class Solution {
public int reverse(int x) {
long result = 0;
while(x != 0){
result = result * 10 + x % 10;
x /= 10;
}
return (int)result == result ? (int)result : 0;
}
}
思路:
long
类型接收int
反转后的值,int
0
。否则,返回该值。判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
示例1:
输入: 121
输出: true
示例二:
输入: -121
输出: false
解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例三:
输入: 10
输出: false
解释: 从右向左读, 为 01 。因此它不是一个回文数。
进阶:
你能不将整数转为字符串来解决这个问题吗?
x < 0
,则一定 非回文数,所以直接return falsetemp
与 x
作比较,若相等,则为回文数;class Solution {
public boolean isPalindrome(int x) {
int origin = x;
int temp = 0;
if (x < 0) {
return false;
}
while (x != 0) {
temp = temp * 10 + x % 10;
x /= 10;
}
return temp == origin ? true : false;
}
}
结果:成功
x < 0
,则一定 非回文数,所以直接return falseint
转换成 string
class Solution {
public boolean isPalindrome(int x) {
String stringX = String.valueOf(x);
if (x < 0) {
return false;
}
for (int i = 0, j = stringX.length() - 1; i < stringX.length(); i++, j--) {
if (j <= i) {
return true;
}
if (stringX.charAt(i) != stringX.charAt(j)) {
return false;
}
}
return true;
}
}
结果:成功
class Solution {
public boolean isPalindrome(int x) {
// 结尾为0,则一定不会是回文数。因为开头的数字不可能为0
if (x < 0 || (x % 10 == 0 && x != 0)) return false;
int revertedNumber = 0;
while (x > revertedNumber) {
revertedNumber = revertedNumber * 10 + x % 10;
x /= 10;
}
return x == revertedNumber || x == revertedNumber / 10;
}
}
class Solution {
public boolean isPalindrome(int x) {
String reversedStr = (new StringBuilder(x + "")).reverse().toString();
return (x + "").equals(reversedStr);
}
}
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
例如, 罗马数字 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
。给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
示例 1:
输入: “III”
输出: 3
示例 2:
输入: “IV”
输出: 4
示例 3:
输入: “IX”
输出: 9
示例 4:
输入: “LVIII”
输出: 58
解释: L = 50, V= 5, III = 3.
示例 5:
输入: “MCMXCIV”
输出: 1994
解释: M = 1000, CM = 900, XC = 90, IV = 4.
提示:
题目所给测试用例皆符合罗马数字书写规则,不会出现跨位等情况。
IC 和 IM 这样的例子并不符合题目要求,49 应该写作 XLIX,999 应该写作 CMXCIX 。
class Solution {
public int romanToInt(String s) {
Map<String, Integer> romanNumerals = new HashMap<>();
romanNumerals.put("I", 1);
romanNumerals.put("V", 5);
romanNumerals.put("X", 10);
romanNumerals.put("L", 50);
romanNumerals.put("C", 100);
romanNumerals.put("D", 500);
romanNumerals.put("M", 1000);
romanNumerals.put("IV", 4);
romanNumerals.put("IX", 9);
romanNumerals.put("XL", 40);
romanNumerals.put("XC", 90);
romanNumerals.put("CD", 400);
romanNumerals.put("CM", 900);
int temp = 0;
int result = 0;
int stringLength = s.length();
while (temp < stringLength) {
if (temp + 2 <= stringLength && romanNumerals.containsKey(s.substring(temp, temp + 2))) {
result += romanNumerals.get(s.substring(temp, temp + 2));
temp += 2;
continue;
}
result += romanNumerals.get(String.valueOf(s.charAt(temp)));
temp += 1;
}
return result;
}
}
结果:成功
Smileyan
的代码class Solution {
public int romanToInt(String s) {
s = s.replace("IV","a");
s = s.replace("IX","b");
s = s.replace("XL","c");
s = s.replace("XC","d");
s = s.replace("CD","e");
s = s.replace("CM","f");
int result = 0;
for (int i=0; i<s.length(); i++) {
result += which(s.charAt(i));
}
return result;
}
public int which(char ch) {
switch(ch) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
case 'a': return 4;
case 'b': return 9;
case 'c': return 40;
case 'd': return 90;
case 'e': return 400;
case 'f': return 900;
}
return 0;
}
}
其他的解法都大同小异,这个代码更加的简洁。
Java性能测试的困惑:switch和map的性能比较
看评论区 - Smileyan
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效。
有效字符串需满足:
注意空字符串可被认为是有效字符串。
示例 1:
输入: “()”
输出: true
示例 2:
输入: “()[]{}”
输出: true
示例 3:
输入: “(]”
输出: false
示例 4:
输入: “([)]”
输出: false
示例 5:
输入: “{[]}”
输出: true
true
false
左右括号
的关系放进HashMap
中Stack
的先进后出的特性左括号
,添加进Stack
中右括号
,从HashMap
中得到对应的Value
,用这个Value
与Stack
中的最后一个元素
作比较。若相等,则继续循环
;若不相等,则直接返回false
false
。则判断Stack
中是否有未取出的元素。若存在未取出的元素,则返回false
,否则返回true
。(这是为了避免((
的情况)class Solution {
public boolean isValid(String s) {
// 1. 空字符串直接返回true
if (s.length() == 0) {
return true;
}
// 2. 如果字符段长度 % 2 != 0,则return false
if (s.length() % 2 != 0) {
return false;
}
// 3. 用HashMap存储括号之间的关系
Map<Character, Character> bracketsMap = new HashMap<>();
bracketsMap.put(']', '[');
bracketsMap.put('}', '{');
bracketsMap.put(')', '(');
// 4. 利用LinkedList
LinkedList<Character> stack = new LinkedList<>();
for (Character c : s.toCharArray()) {
// 5. 如果得到的字符在Map的key中,证明它是右括号。那么,从stack中获取元素,它需要与这个Key所对应的Value相等,否则不匹配。
if (bracketsMap.containsKey(c)) {
if (stack.isEmpty() || !stack.pop().equals(bracketsMap.get(c))) {
return false;
}
continue;
}
stack.push(c);
}
return stack.isEmpty() ? true : false;
}
}
结果:成功
执行用时:2 ms
内存消耗:36.7 MB
淺い空
的代码class Solution {
public boolean isValid(String s) {
LinkedList<Character> stack = new LinkedList<>();
for (char c : s.toCharArray()) {
if (c == '[') stack.push(']');
else if (c == '(') stack.push(')');
else if (c == '{') stack.push('}');
else if (stack.isEmpty() || c != stack.pop()) return false;
}
return stack.isEmpty();
}
}
结果:成功
执行用时:1 ms
内存消耗:36.6 MB
思路差不多,果然数据量比较少的时候,可以抛弃Map。
看评论区 - 淺い空
ArrayList和LinkedList和Vector的区别
java集合系列之LinkList
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入: [“flower”,“flow”,“flight”]
输出: “fl”
示例 2:
输入: [“dog”,“racecar”,“car”]
输出: “”
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z
。
字符串数组
按照长度
,从小到大
排序,拿到最小的字符串长度
HashSet
中HashSet
中元素不可重复
的原理,每一次遍历下来,如果HashSet
中的长度超过1
,则证明此次循环中的前缀不一致,返回上一次记录的前缀class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs.length == 0){
return "";
}
String result = "";
Set<String> stringSet = new HashSet<>();
// 按照长度排序,获得字符串的最小长度,超过最小长度的字符串就不再需要判断了
Arrays.sort(strs, (x, y) -> {
return x.length() <= y.length() ? -1 : 1;
});
int minLength = strs[0].length();
int index = 1;
while (index <= minLength) {
for (String data : strs) {
stringSet.add(data.substring(0, index));
}
// 用HashSet排除重复元素,如果一次循环下来,HashSet的长度大于1,则证明此次循环中,有不同的前缀字符
if (stringSet.size() > 1) {
break;
} else {
result = stringSet.iterator().next();
stringSet.clear();
index++;
}
}
return result;
}
}
结果:成功
官方提出了4种解题思路:
其中 横向扫描 和 纵向扫描 的原理其实类似,我个人解题思路偏向于 纵向扫描。
分治 的解题思路,在横向扫描的基础上,将字符串数组切分计算再做整合。
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
} else {
return longestCommonPrefix(strs, 0, strs.length - 1);
}
}
public String longestCommonPrefix(String[] strs, int start, int end) {
if (start == end) {
return strs[start];
} else {
int mid = (end - start) / 2 + start;
String lcpLeft = longestCommonPrefix(strs, start, mid);
String lcpRight = longestCommonPrefix(strs, mid + 1, end);
return commonPrefix(lcpLeft, lcpRight);
}
}
public String commonPrefix(String lcpLeft, String lcpRight) {
int minLength = Math.min(lcpLeft.length(), lcpRight.length());
for (int i = 0; i < minLength; i++) {
if (lcpLeft.charAt(i) != lcpRight.charAt(i)) {
return lcpLeft.substring(0, i);
}
}
return lcpLeft.substring(0, minLength);
}
}
class Solution {
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
int minLength = Integer.MAX_VALUE;
for (String str : strs) {
minLength = Math.min(minLength, str.length());
}
int low = 0, high = minLength;
while (low < high) {
int mid = (high - low + 1) / 2 + low;
if (isCommonPrefix(strs, mid)) {
low = mid;
} else {
high = mid - 1;
}
}
return strs[0].substring(0, low);
}
public boolean isCommonPrefix(String[] strs, int length) {
String str0 = strs[0].substring(0, length);
int count = strs.length;
for (int i = 1; i < count; i++) {
String str = strs[i];
for (int j = 0; j < length; j++) {
if (str0.charAt(j) != str.charAt(j)) {
return false;
}
}
}
return true;
}
}
流程:
flower
、flow
、flight
4
flower.substring(0, 2) = fl
fl
与 flow
& flight
的前缀对比,return true
flower.substring(0, 3) = flo
flo
与 flow
& flight
的前缀对比,return false
2
low < high = 2 < 2 = false
flower.substring(0, 2) = fl
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
看了一眼官方给出的两个思路:迭代 & 递归,一开始看题目的时候,大致思路是一致的,就是写代码的时候写的一塌糊涂。(吐了~菜是原罪)
耗时:2.5h
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
}
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
给定一个排序数组
,你需要在 原地
删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间
,你必须在 原地
修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
class Solution {
public int removeDuplicates(int[] nums) {
if (nums.length < 2) {
return 1;
}
int i = 0, j = 1;
while (j < nums.length) {
if (nums[i] == nums[j]) {
j++;
} else {
nums[i + 1] = nums[j];
i++;
j++;
}
}
return i + 1;
}
}
给你一个数组 nums
和一个值 val
,你需要 原地
移除所有数值等于 val
的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地
修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
此题思路和【LeetCode刷题记录】 - 删除排序数组中的重复项是一样的。
注意题目中的提醒:
class Solution {
public int removeElement(int[] nums, int val) {
int i = 0, j = 0;
while (j < nums.length) {
if (nums[j] == val) {
j++;
} else {
nums[i] = nums[j];
i++;
j++;
}
}
return i;
}
}
用的也是
双指针
的方式
实现 strStr()
函数。
给定一个 haystack
字符串和一个 needle
字符串,在 haystack
字符串中找出 needle
字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1
。
示例 1:
输入: haystack = “hello”, needle = “ll”
输出: 2
示例 2:
输入: haystack = “aaaaa”, needle = “bba”
输出: -1
说明:
当 needle
是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle
是空字符串时我们应当返回 0
。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
needle
为空,则返回 0
haystack
和 needle
长度相等,且字符串相等,则返回0
haystack
的长度小于needle
,则返回-2
haystack
和 needle
长度相等,但是字符串不相等,则返回-2
haystack
中截取与needle
长度一致的字符串进行比较。得到我们想要的结果class Solution {
public int strStr(String haystack, String needle) {
if (needle.length() == 0 || (haystack.length() == needle.length() && needle.equals(haystack))) {
return 0;
}
if (haystack.length() < needle.length() || (haystack.length() == needle.length() && !needle.equals(haystack))) {
return -1;
}
for (int i = 0, j = needle.length(); j <= haystack.length(); i++, j++) {
if (haystack.substring(i, j).equals(needle)) {
return i;
}
}
return -1;
}
}
结果:成功
class Solution {
public int strStr(String haystack, String needle) {
int L = needle.length(), n = haystack.length();
for (int start = 0; start < n - L + 1; ++start) {
if (haystack.substring(start, start + L).equals(needle)) {
return start;
}
}
return -1;
}
}
上一个方法的缺陷是会将 haystack 所有长度为 L 的子串都与 needle 字符串比较,实际上是不需要这么做的。
首先,只有子串的第一个字符跟 needle 字符串第一个字符相同的时候才需要比较。
其次,可以一个字符一个字符比较,一旦不匹配了就立刻终止。
如下图所示,比较到最后一位时发现不匹配,这时候开始回溯。需要注意的是,pn 指针是移动到 pn = pn - curr_len + 1 的位置,而 不是 pn = pn - curr_len 的位置。
这时候再比较一次,就找到了完整匹配的子串,直接返回子串的开始位置 pn - L。
class Solution {
public int strStr(String haystack, String needle) {
int L = needle.length(), n = haystack.length();
if (L == 0) return 0;
int pn = 0;
while (pn < n - L + 1) {
// find the position of the first needle character
// in the haystack string
while (pn < n - L + 1 && haystack.charAt(pn) != needle.charAt(0)) ++pn;
// compute the max match string
int currLen = 0, pL = 0;
while (pL < L && pn < n && haystack.charAt(pn) == needle.charAt(pL)) {
++pn;
++pL;
++currLen;
}
// if the whole needle string is found,
// return its start position
if (currLen == L) return pn - L;
// otherwise, backtrack
pn = pn - currLen + 1;
}
return -1;
}
}
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2
示例 2:
输入: [1,3,5,6], 2
输出: 1
示例 3:
输入: [1,3,5,6], 7
输出: 4
示例 4:
输入: [1,3,5,6], 0
输出: 0
整个数组遍历一次,遇到小于等于某个值时,返回该值下标。
class Solution {
public int searchInsert(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
if (target <= nums[i]) {
return i;
}
}
return nums.length;
}
}
class Solution {
public int searchInsert(int[] nums, int target) {
int min = 0;
int max = nums.length;
while (min < max) {
int middle = (max + min) / 2;
if (nums[middle] >= target) {
max = middle;
} else {
min = middle + 1;
}
}
return min;
}
}
实现 int sqrt(int x)
函数。
计算并返回 x 的平方根
,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
示例 1:
输入: 4
输出: 2
示例 2:
输入: 8
输出: 2
说明: 8 的平方根是 2.82842…,
由于返回类型是整数,小数部分将被舍去。
class Solution {
public int mySqrt(int x) {
int min = 0;
int max = x;
if (x == 1) {
return 1;
}
while (max - min > 1) {
int mid = (max - min) / 2 + min;
if (x / mid >= mid) {
min = mid;
} else {
max = mid;
}
}
return min;
}
}
给定一个已按照升序排列
的有序
数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1
和 index2
,其中 index1
必须小于 index2
。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。
class Solution {
public int[] twoSum(int[] numbers, int target) {
for (int i = 0; i < numbers.length; i++) {
int min = i + 1;
int max = numbers.length - 1;
while (min <= max) {
int middle = (max - min) / 2 + min;
if (numbers[i] + numbers[middle] == target) {
return new int[]{i + 1, middle + 1};
} else if (numbers[i] + numbers[middle] > target) {
max = middle - 1;
} else {
min = middle + 1;
}
}
}
return null;
}
}
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n
个版本 [1, 2, ..., n]
,你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version)
接口来判断版本号 version
是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
int left = 1;
int right = n;
while (left < right) {
int mid = left + (right - left) / 2;
if (isBadVersion(mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
给定一个整数数组 nums
,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
暴力破解,用Set
记录下每一次计算的结果,最后返回最大值
class Solution {
public int maxSubArray(int[] nums) {
Set<Integer> resultSet = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
int sum = 0;
for (int j = i; j < nums.length; j++) {
sum += nums[j];
resultSet.add(sum);
}
resultSet.add(nums[i]);
}
return resultSet.stream().max((x, y) -> x > y ? 1 : -1).orElse(0);
}
}
结果:失败,超出时间限制
sum
,结果为 ans
sum > 0
,则说明 sum
对结果有增益效果,则 sum
保留并加上当前遍历数字sum <= 0
,则说明 sum
对结果无增益效果,需要舍弃,则 sum
直接更新为当前遍历数字sum
和 ans
的大小,将最大值置为ans
,遍历结束返回结果O(n)
class Solution {
public int maxSubArray(int[] nums) {
int ans = nums[0];
int sum = 0;
for(int num: nums) {
if(sum > 0) {
sum += num;
} else {
sum = num;
}
ans = Math.max(ans, sum);
}
return ans;
}
}
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
示例 2:
输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
示例 3:
输入:digits = [0]
输出:[1]
提示:
1 <= digits.length <= 100
0 <= digits[i] <= 9
class Solution {
public int[] plusOne(int[] digits) {
StringBuilder stringBuilder = new StringBuilder();
for (int digit : digits) {
stringBuilder.append(digit);
}
Long digit = Long.valueOf(stringBuilder.toString());
digit += 1;
String stringResult = String.valueOf(digit);
int length = stringResult.length();
int[] result = new int[length];
for (int i = 0; i < length; i++) {
result[i] = Integer.valueOf(String.valueOf(stringResult.charAt(i)));
}
return result;
}
}
结果:失败
class Solution {
public int[] plusOne(int[] digits) {
for (int i = digits.length - 1; i >= 0; i--) {
// 不等于9,加一返回
if (digits[i] != 9) {
digits[i] += 1;
return digits;
}
// 等于9,修改为0
digits[i] = 0;
}
// 给的数组中所有下标对应的值都为9,则初始化新的数组,首位为1,其余皆为0
int[] result = new int[digits.length + 1];
result[0] = 1;
return result;
}
}
给定一个正整数 n
,输出外观数列的第 n
项。
「外观数列」是一个整数序列
,从数字 1
开始,序列中的每一项都是对前一项的描述
。
你可以将其视作是由递归公式定义的数字字符串序列:
前五项如下:
- 1
- 11
- 21
- 1211
- 111221
第一项是数字 1
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 “11”
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 “21”
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 “1211”
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 “111221”
要 描述
一个数字字符串,首先要将字符串分割为 最小 数量的组,每个组都由连续的最多 相同字符 组成。然后对于每个组,先描述字符的数量,然后描述字符,形成一个描述组。要将描述转换为数字字符串,先将每组中的字符数量用数字替换,再将所有描述组连接起来。
例如,数字字符串 "3322251"
的描述如下图:
示例 1:
输入:n = 1
输出:“1”
解释:这是一个基本样例。
示例 2:
输入:n = 4
输出:“1211”
解释:
countAndSay(1) = “1”
countAndSay(2) = 读 “1” = 一 个 1 = “11”
countAndSay(3) = 读 “11” = 二 个 1 = “21”
countAndSay(4) = 读 “21” = 一 个 2 + 一 个 1 = “12” + “11” = “1211”
提示:
主要是记住同样的数字出现的次数,如:21
之后的数:
数字2
出现了 1次
,记为:12
数字1
出现了 1次
,记为:11
1211
class Solution {
public String countAndSay(int n) {
String preResult = "1";
if (n == 1) {
return preResult;
}
StringBuilder result = new StringBuilder();
for (int i = 2; i <= n; i++) {
int count = 0;
char target = preResult.charAt(0);
result = new StringBuilder();
for (int j = 0; j < preResult.length(); j++) {
if (target != preResult.charAt(j)) {
result.append(count);
result.append(target);
target = preResult.charAt(j);
count = 1;
} else {
count++;
}
if (j + 1 == preResult.length()) {
result.append(count);
result.append(target);
}
}
preResult = result.toString();
}
return result.toString();
}
}
class Solution {
public String countAndSay(int n) {
String preResult = "1";
if (n == 1) {
return preResult;
}
for (int i = 2; i <= n; i++) {
int count = 1;
char target = preResult.charAt(0);
StringBuilder result = new StringBuilder();
for (int j = 1; j < preResult.length(); j++) {
if (target == preResult.charAt(j)) {
count++;
} else {
result.append(count);
result.append(target);
target = preResult.charAt(j);
count = 1;
}
}
result.append(count);
result.append(target);
preResult = result.toString();
}
return preResult;
}
}
比如 n=6时,那么用递归得到上一层的字符串str=“111221”
我们将start指向下标0,我们从下标1开始遍历,遍历到“2”下标3的时候,sb拼上(3-0)个1即sb.append(3).append(1),将start指针指向下标3,接着重复以上操作,直到到达str的最后一位,sb直接拼上即可。
class Solution {
public String countAndSay(int n) {
// 递归终止条件
if (n == 1) {
return "1";
}
StringBuffer res = new StringBuffer();
// 拿到上一层的字符串
String str = countAndSay(n - 1);
int length = str.length();
// 开始指针为0
int start = 0;
// 注意这从起始条件要和下面长度统一
for (int i = 1; i < length + 1; i++) {
// 字符串最后一位直接拼接
if (i == length) {
res.append(i - start).append(str.charAt(start));
// 直到start位的字符串和i位的字符串不同,拼接并更新start位
} else if (str.charAt(i) != str.charAt(start) ) {
res.append(i - start).append(str.charAt(start));
start = i;
}
}
return res.toString();
}
}
给你一个非空数组,返回此数组中 第三大的数 。如果不存在,则返回数组中最大的数。
示例1:
输入:[3, 2, 1]
输出:1
解释:第三大的数是 1 。
示例2:
输入:[1, 2]
输出:2
解释:第三大的数不存在, 所以返回最大的数 2 。
示例3:
输入:[2, 2, 3, 1]
输出:1
解释:注意,要求返回第三大的数,是指在所有不同数字中排第三大的数。
此例中存在两个值为 2 的数,它们都排第二。在所有不同数字中排第三大的数为 1 。
使用TreeSet
排序 + 去重,如果最后得到的集合大小 <= 2
,则取最大值(treeSet.last())
。否则,取第三大的值。
public int thirdMax(int[] nums) {
TreeSet<Integer> treeSet = new TreeSet<>();
for (int num : nums) {
treeSet.add(num);
}
if (treeSet.size() <= 2) {
return treeSet.last();
}
treeSet.pollLast();
treeSet.pollLast();
return treeSet.last();
}
还是使用TreeSet
排序 + 去重,不过保持集合的大小不超过3,如果超过3,则remove
最小值。
根据最后得到的集合大小判断,如果最后得到的集合大小 <= 2
,则取集合的最大值(treeSet.last())
。否则,取集合的最小值(treeSet.first())
。
public int thirdMax(int[] nums) {
TreeSet<Integer> treeSet = new TreeSet<>();
for (int num : nums) {
treeSet.add(num);
if (treeSet.size() > 3) {
// 拿到最小的值,删除
treeSet.remove(treeSet.pollFirst());
}
}
return treeSet.size() >= 3 ? treeSet.first() : treeSet.last();
}
不需要排序 + 一次遍历
public int thirdMax3(int[] nums) {
Integer a = null;
Integer b = null;
Integer c = null;
for (int num : nums) {
if (a == null || num > a) {
c = b;
b = a;
a = num;
continue;
}
if ((b == null || num > b) && a > num) {
c = b;
b = num;
continue;
}
if ((c == null || num > c) && (b != null && b > num)) {
c = num;
}
}
return c == null ? a : c;
}
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
示例 1:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入:height = [1,1]
输出:1
提示:
// 暴力破解,双循环,超出时间限制
public int maxArea(int[] height) {
int maxArea = 0;
for (int i = 0; i < height.length - 1; i++) {
for (int j = i + 1; j < height.length; j++) {
// 求出当前两个距离的面积
int currentArea = Math.min(height[i], height[j]) * (j - i);
// 取得最大面积
maxArea = Math.max(maxArea, currentArea);
}
}
return maxArea;
}
// 双指针
public int maxArea(int[] height) {
int maxArea = 0;
int left = 0;
int right = height.length - 1;
while (left < right) {
// 求出当前两个距离的面积
int currentArea = Math.min(height[left], height[right]) * (right - left);
// 取得最大面积
maxArea = Math.max(maxArea, currentArea);
if (height[left] < height[right]) {
left++;
} else {
right++;
}
}
return maxArea;
}