LeetCode刷题
Type:Medium
https://leetcode-cn.com/problemset/all/
#8 字符串转换整数
需要注意几个测试用例
" +0 123"
" 0012"
"3.14"
先判断是否有正负号,然后循环数组,需要记得把 Character.isDigit(sc[i])
加在循环条件里,保证只有当下一个字符是数字的时候才会把数值加起来。
class Solution {
public int myAtoi(String str) {
boolean isPos = true;
char[] sc = (str.trim() + " ").toCharArray();
int i = 0;
if (sc[0] == '-') {
isPos = false;
i++;
} else if (sc[0] == '+') {
isPos = true;
i++;
}
long value = 0;
while (i < sc.length && Character.isDigit(sc[i])) {
value = value * 10 + (sc[i] - '0');
i++;
if (value > Integer.MAX_VALUE) {
return isPos ? Integer.MAX_VALUE : Integer.MIN_VALUE;
}
}
value = isPos ? value : -value;
return (int) value;
}
}
#15 三数之和
首先对原数组进行排序。
最先想到的是三重循环,优化:
要满足 a + b + c = 0 ,则 a 一定是非正数,即 i 对应的数一定要小于等于 0 的,等于0的时候 a b c 都为 0 。同时 i 的循环应该是从 0 到 length - 3 的,即 0 <= i < length - 2 , 因为 j 要大于 i ,同理 k 要大于 j 。
实现如下:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(nums);
int v1;
int v2;
int v3;
for(int i = 0; i < nums.length-1 && nums[i] <= 0; ++i){
v1 = nums[i];
int j = i + 1;
int k = nums.length - 1;
while(k > j){
v2 = nums[j];
v3 = nums[k];
if(v1 + v2 + v3 > 0){
k--;
}else if(v1 + v2 + v3 < 0){
j++;
}
else if(v1 + v2 + v3 == 0){
List<Integer> tmp = new ArrayList<>();
tmp.add(v1);
tmp.add(v2);
tmp.add(v3);
if(!res.contains(tmp)){a
res.add(tmp);
}
j++;
k--;
}
}
}
return res;
}
}
上面这种写法虽然能满足题目,但是会超时,时间主要会消耗在 res.add() 之前的去重上。所以想通过 HashSet 来辅助去重(Hash表的查询时间O(1)因为查询的是每个元素的hashcode)。
想要可以自动拓展的数组 => List
想要元素不重复的数组 => Set
set内部排序由每个元素的hashcode决定
双重循环,v3 = -(v1 + v2) , 借助 HashMap 来判断原数组是否有 v3 这个值同时下标必须在 v2 之后,满足的话就可以放进 Set 中。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Set<List<Integer>> res = new HashSet<>();
Map<Integer,Integer> map = new HashMap<>();
Arrays.sort(nums);
for(int i = 0;i < nums.length;++i){
map.put(nums[i],i);
}
int v1;
int v2;
int v3;
for(int i = 0; i < nums.length - 2 && nums[i] <= 0; ++i){
for(int j = i + 1; j < nums.length - 1 && j > i; ++j){
v1 = nums[i];
v2 = nums[j];
v3 = -(v1 + v2);
if(map.containsKey(v3) && map.get(v3) > j){
res.add(Arrays.asList(v1,v2,v3));
}
}
}
return new ArrayList<>(res);
}
}
#701 二叉搜索树中的插入操作
current 为当前结点,如果当前结点为 null ,那么就记录下 current 对应的 parent 结点,插入值小于 parent 结点的值,插入在左子树,反之插入在右子树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode insertIntoBST(TreeNode root, int val) {
if(root == null){
root = new TreeNode(val);
return root;
}
boolean isLeft = false;
TreeNode current = root;
TreeNode parent = root;
while(current != null){
parent = current;
if(val < current.val){
current = current.left;
isLeft = true;
}else{
current = current.right;
isLeft = false;
}
}
TreeNode vNode = new TreeNode(val);
if(isLeft == true){
parent.left = vNode;
}else{
parent.right = vNode;
}
return root;
}
}
#2 两数相加
不能将链表中的每个元素一次性全部取出然后相加,然后把计算后的数值每一位都插入到链表中,因为可能会超出int整数范围。所以只能同时遍历两个链表对每一个元素依次相加。
需要用到的是一个进位符号,如果当前位置加和大于等于10,进位置为1;还需要一个curNode,作为辅助指针,指向当前应该插入的位置。
需要注意的测试用例是:
[9],[9]
输出的结果应该是[8,1],首先当两数相加大于等于10的时候,sum应该等于对10取余。同时如果 l1 和 l2 如果都计算完了,但是进位标识是1的话还需要再添加一个元素进去。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int carry = 0; // 进位
ListNode head = new ListNode(0);
ListNode curNode = head; // 辅助指针
while(l1 != null || l2 != null){
int sum = tmp;
if(l1 != null){
sum += l1.val;
}
if(l2 != null){
sum += l2.val;
}
ListNode tmpNode;
if(sum >= 10){
carry = 1;
tmpNode = new ListNode(sum % 10);
}else{
carry = 0;
tmpNode = new ListNode(sum);
}
curNode.next = tmpNode;
curNode = curNode.next;
if(l1 != null){
l1 = l1.next;
}
if(l2 != null){
l2 = l2.next;
}
}
if(carry == 1){
ListNode tmpNode = new ListNode(1);
curNode.next = tmpNode;
}
return head.next;
}
}
#120 三角形最小路径和
从最后一层往上计算,当前一层的值等于下一层相邻的两个数取最小再加上当前一层的数,不断网上,一直到第一层。
第一种方式:借助一个 n + 1 大小的数组,记录每一层的值,(最后一层是初始化的过程),然后输出数组第一个值就是最小路径和。
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
if(triangle == null || triangle.size() == 0){
return 0;
}
int[] res = new int[triangle.size()+1];
for(int i = triangle.size() - 1; i >= 0; --i){
for(int j = 0; j < triangle.get(i).size(); ++j){
res[j] = Math.min(res[j],res[j+1]) + triangle.get(i).get(j);
}
}
return res[0];
}
}
第二种方式:从倒数第二层开始计算,计算出当前位置的最小值后,直接重新赋值,不需要新的空间
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
if(triangle == null || triangle.size() == 0){
return 0;
}
for(int i = triangle.size() - 2; i >= 0; --i){
for(int j = 0; j < triangle.get(i).size(); ++j){
Integer re = Math.min(triangle.get(i+1).get(j),triangle.get(i+1).get(j+1)) + triangle.get(i).get(j);
triangle.get(i).set(j,re);
}
}
return triangle.get(0).get(0);
}
}
#220 存在重复元素Ⅲ
跟 #217 #219 关联性不大,#217 是找到重复元素,#219是找到重复元素并且是在一定范围内。这道题是要保证在一定的范围内(k),两个值的差的绝对值要不大于某个数(t)。
前面的 #217 可以使用双重循环运行完所有的测试用例,但是 #220 使用双重循环就会超时,所以使用 treeset 来辅助计算,二叉树的插入、删除、搜索都是O(logN)。总的时间复杂度降为O(N * logN)。
修改前:
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if(nums.length == 0 || k <= 0)
return false;
for(int i = 0; i < nums.length; ++i){
for(int j = i+1; j <= i + k; ++j){
if(j >= nums.length)
break;
long val = 0; // 用 long 是为了避免 int 超出范围
val = Math.abs((long)nums[i] - (long)nums[j]) > val ? Math.abs((long)nums[i] - (long)nums[j]) : val;
if(val <= t)
return true;
}
}
return false;
}
}
修改后:
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if(nums.length < 2 || k < 1 || t < 0)
return false;
TreeSet <Long> set = new TreeSet<Long>();
for(int i = 0; i < nums.length; ++i){
// set.subSet() 返回的类型是 SortedSet,返回值的范围是 [(long)nums[i]-t,(long)nums[i]+t+1))
SortedSet <Long> subSet = set.subSet((long)nums[i]-t,(long)nums[i]+t+1);
if(!subSet.isEmpty())
return true;
if(i >= k)
set.remove((long)nums[i-k]);
set.add((long)nums[i]);
}
return false;
}
}
#881 救生艇
贪心(双指针)
思路
如果最重的人可以与最轻的人共用一艘船,那么就这样安排。否则,最重的人无法与任何人配对,那么他们将自己独自乘一艘船。
这么做的原因是,如果最轻的人可以与任何人配对,那么他们也可以与最重的人配对。
算法
令 people[i] 指向当前最轻的人,而 people[j] 指向最重的那位。
然后,如上所述,如果最重的人可以与最轻的人共用一条船(即 people[j] + people[i] <= limit),那么就这样做;否则,最重的人自己独自坐在船上
class Solution {
public int numRescueBoats(int[] people, int limit) {
Arrays.sort(people);
int i = 0;
int j = people.length - 1;
int boat = 0;
while(i <= j){
boat++;
if(people[i] + people[j] <= limit){
i++;
}
j--;
}
return boat;
}
}
#35 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
二分查找
class Solution {
public int searchInsert(int[] nums, int target) {
if(target < nums[0]){
return 0;
}else if(target > nums[nums.length - 1]){
return nums.length;
}else {
int low = 0;
int high = nums.length - 1;
while(low <= high){
int mid = (low + high) / 2;
if(target < nums[mid]){
high = mid - 1;
}else if(target == nums[mid]){
return mid;
}else {
low = mid + 1;
}
}
return low;
}
}
}
#74 搜索二维矩阵
先查找符合的行,然后下标从 1 … matrix[0].length - 2 通过二分查找来查找元素
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix.length == 0 || matrix[0].length == 0){
return false;
}
int low = 0;
int high = matrix.length - 1;
while(low <= high){
int mid = (low + high) / 2;
if(target < matrix[mid][0]){
// 在 1 3 5 7 中查询
high = mid - 1;
}else if(target == matrix[mid][0] || target == matrix[mid][matrix[0].length - 1]){
return true;
}else if(target > matrix[mid][matrix[0].length - 1]){
// 在 23 30 34 50 中查询
low = mid + 1;
}else {
// 在 10 11 16 20 中查询
int l = 1; // 已经排除 10 和 20 不可能了
int h = matrix[0].length - 2;
while(l <= h){
int m = (l + h) / 2;
if(target < matrix[mid][m]){
h = m - 1;
}else if(target == matrix[mid][m]){
return true;
}else {
l = m + 1;
}
}
// 缺少这个 return 的话,会一直死循环
return false;
}
}
return false;
}
}
#240 搜索二维矩阵Ⅱ
矩阵具有以下特性:
1.每行的元素从左到右升序排列。
2.每列的元素从上到下升序排列。
从左下或者右上开始判断都可以,按台阶形状进行判断
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix.length == 0 || matrix[0].length == 0){
return false;
}
// 从左下开始判断
// int i = matrix.length - 1;
// int j = 0;
// while(i >= 0 && j < matrix[0].length){
// if(matrix[i][j] == target){
// return true;
// }else if(matrix[i][j] < target){
// j = j + 1;
// }else {
// i = i - 1;
// }
// }
// 从右上开始判断
int i = 0;
int j = matrix[0].length - 1;
while(i < matrix.length && j >= 0){
if(matrix[i][j] == target){
return true;
}else if(matrix[i][j] > target){
j = j - 1;
}else {
i = i + 1;
}
}
return false;
}
}
#11 盛最多水的容器
Area = Max ( min ( height[i], height[j] ) * ( j-i ) ) { 其中 0 <= i < j < height.size() }
使用两个指针,值小的指针向内移动,这样就减小了搜索空间。因为面积取决于指针的距离与值小的值乘积,如果值大的值向内移动,距离一定减小,而求面积的另外一个乘数一定小于等于值小的值,因此面积一定减小,而我们要求最大的面积,因此值大的指针不动,而值小的指针向内移动遍历。
class Solution {
public int maxArea(int[] height) {
int i = 0;
int j = height.length - 1;
int area = 0;
while(i < j){
int h = Math.min(height[i], height[j]);
area = Math.max(area, (j - i) * h);
if(h == height[i])
i++;
else
j--;
}
return area;
}
}