[LeetCode力扣][简单难度]算法思想和代码(持续更新)

426 阅读9分钟

1.(2019.5.30)【1】给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

示例: 给定 nums = [2, 7, 11, 15], target = 9 因为 nums[0] + nums[1] = 2 + 7 = 9 所以返回 [0, 1]

  • 【算法思想】

(1)️暴力求解,嵌套遍历数组。 外层循环i由下标0到下标length-1,内层循环:j由下标i+1到下标length。结束循环条件:nums[i]+nums[j]=target。返回i和j。时间复杂度为O(n*n)

class Solution {
    public int[] twoSum(int[] nums, int target) {
         int[] x=new int[2];
        for (int i = 0; i < nums.length-1; i++) {
            for (int j = i+1 ; j < nums.length; j++) {
                if (nums[j] + nums[i] == target) {
                    x[0]=i;
                    x[1]=j;
                    return x;
                }
            }
        }
        return x;
    }
}
执行结果
输入  [2,7,11,15]  9
输出  [0,1]
预期结果  [0,1]
执行用时 : 28 ms, 在Two Sum的Java提交中击败了56.75% 的用户
内存消耗 : 36.9 MB, 在Two Sum的Java提交中击败了93.30% 的用户

(2)哈希优化

以空间换时间的方法,我们可以将查找时间从 O(n) 降低到O(1)。哈希表支持以 近似 恒定的时间进行快速查找。在第一次迭代中,将每个元素的值和它的索引添加到表中。然后,在第二次迭代中,检查每个元素所对应的目标元素(target -nums[i]) 是否存在于表中。且该目标元素不能是nums[i] 本身!

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> m = new HashMap<>();  //前key,后值
        int[] x = new int[2];
        for (int i = 0; i < nums.length; i++) {
            m.put(nums[i], i);
        }
        for (int i = 0; i < nums.length; i++) {
            if (m.containsKey(target - nums[i]) && m.get(target - nums[i]) != i) {
                x[0] = i;
                x[1] = m.get(target - nums[i]);
                return x;
            }
        }
        return x;
    }
}
执行用时 : 12 ms, 在Two Sum的Java提交中击败了62.11% 的用户
内存消耗 : 38.8 MB, 在Two Sum的Java提交中击败了52.39% 的用户

时间复杂度:O(n), 我们把包含有 n 个元素的列表遍历两次。由于哈希表将查找时间为 O(1) ,所以时间复杂度为 O(n)。

空间复杂度:O(n), 所需的额外空间取决于哈希表中存储的元素数量,该表中存储了 n 个元素。

(3)一遍哈希优化

基于以上哈希优化方法,可以简化到一趟就同时完成判断和初始化。先进行判断,若在表中则立即结束,否则继续进行初始化。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> m = new HashMap<>();  //前key,后值
        int[] x = new int[2];
        
        for (int i = 0; i < nums.length; i++) {
            if (m.containsKey(target - nums[i]) && m.get(target - nums[i]) != i) {
                x[0] = i;
                x[1] = m.get(target - nums[i]);
                return x;
            }
            m.put(nums[i], i);
        }
        return x;
    }
}
执行用时 : 9 ms, 在Two Sum的Java提交中击败了77.64% 的用户
内存消耗 : 39 MB, 在Two Sum的Java提交中击败了49.37% 的用户

时间复杂度和空间复杂度都为:O(n) 。

2.(2019.6.25)【9】判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

示例 1: 输入: 121 输出: true

示例 2: 输入: -121 输出: false 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。

示例 3: 输入: 10 输出: false 解释: 从右向左读, 为 01 。因此它不是一个回文数。

  • 【算法思想】

将输入的整数转换成String类,最简单的方法就是在前面家一个“”:String s=""+x。然后用一个循环逐位判断string的第i个和最后第i个字符是否相等,只要有不等,就立即返回false。

class Solution {
    public boolean isPalindrome(int x) {
        String s=""+x;
        int n=s.length();

        for(int i=0;i<n/2;i++){
            if(s.charAt(i)!=s.charAt(n-i-1)){
                return false;
            }
        }
        return true;
    }
}

执行结果

执行用时 :53 ms, 在所有 Java 提交中击败了57.23%的用户
内存消耗 :39.2 MB, 在所有 Java 提交中击败了82.73%的用户

算法复杂度:O(n),需要遍历一遍string

3.(2019.6.26)【20】给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

注意空字符串可被认为是有效字符串。

示例 1: 输入: "()" 输出: true

示例 2: 输入: "()[]{}" 输出: true

示例 3: 输入: "(]" 输出: false

示例 4: 输入: "([)]" 输出: false

示例 5: 输入: "{[]}" 输出: true

  • 【算法思想】

括号匹配是非常经典的栈的应用。遇到左括号进栈,遇到右括号弹出栈进行匹配。需要注意以下几个关键点: 1)java有线程的Stack类,实例化的时候要指定类型Stack<Character> st=new Stack<Character>();。 2)当遇到右括号是,按理此时栈中应该有左括号,然而不排除此时栈为空,栈为空时会抛出EmptyStackException异常,解决方法是try-catch,try中尝试获得栈顶,若栈为空即异常时,返回false即不匹配,否则才出栈。 3)用switch分别判断三种括号是否匹配,若匹配则break继续循环,否则返回false即不匹配。 4)在循环结束后,还要判断栈是否为空,因为不排除还有剩余的左括号未被匹配的情况。

import java.util.*;
class Solution {
     public boolean isValid(String s) {
        Stack<Character> st=new Stack<Character>();
        int n=s.length();
        for(int i=0;i<n;i++){
            char c=s.charAt(i);
            if(c=='('||c=='['||c=='{'){
                st.push(c);
            }
            else {
                try{
                    char temp=st.peek();
                } catch(EmptyStackException e) {
                    return false;
                }
                char ch=st.pop();

                switch (c){
                    case ')':if(ch=='(')break;else return false;
                    case ']':if(ch=='[')break;else return false;
                    case '}':if(ch=='{')break;else return false;
                    default:return false;
                }
            }
        }
        if(!st.empty()){
            return false;
        }
        else{
            return true;
        }
    }
}

执行结果:

执行用时 :4 ms, 在所有 Java 提交中击败了89.88%的用户
内存消耗 :34.7 MB, 在所有 Java 提交中击败了82.91%的用户

时间复杂度:O(n)需要遍历一遍String

空间复杂度:O(n),需要建立一个栈,极端情况下大小为n(全部都是左括号时),平均情况是n/2。

4.(2019.6.27)【21】将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4

输出:1->1->2->3->4->4

  • 【算法思想】

用节约空间的做法就是,只新建一个头结点作为新链表,同时从头遍l1、l2两个链表,将较小的节点从链表上断开接到新的链表的尾部,直到l1或l2不含任何节点,将另一个链表的剩余节点接到新链表尾部,返回新链表。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode l=new ListNode(0);
        ListNode head=l;
        while(l1!=null&&l2!=null){
            if(l1.val<l2.val){
                l.next=l1;
                l1=l1.next;
            }
            else{
                l.next=l2;
                l2=l2.next;
            }
            l=l.next;
            l.next=null;
        }
        if(l1!=null){
            l.next=l1;
        }
        else{
            l.next=l2;
        }
        return head.next;
    }
}

运行结果

执行用时 :1 ms, 在所有 Java 提交中击败了99.78%的用户
内存消耗 :35.9 MB, 在所有 Java 提交中击败了87.54%的用户

时间复杂度:O(n+m),会遍历两个链表

空间复杂度:O(1)仅需建立一个新节点

5.(2019.6.28)【53】给定一个整数数组 nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

  • 算法思想

temp=0,max为Integer.MIN_VALUE即最小的整数,实际上值为-2147483648。遍历整个数组,将temp加当前值,先判断:若大于max,则更新max,再判断:若temp小于0,更新temp=0,则从下一个值重新计算。重新计算的原因是,之前的数的和都小于0了(并且已经更新了最大值max了),后面若出现更大值,则也不会加上之前的数,因为是负数了。

class Solution {
   public int maxSubArray(int[] nums) {
        int n=nums.length;
        int temp=0;
        int max=Integer.MIN_VALUE;   //-2147483648
        for(int i=0;i<n;i++){
            temp=temp+nums[i];
            if(temp>max){
                max=temp;
            }
            if(temp<=0){
                temp=0;
                continue;
            }
            
        }
        return max;
    }
}

运行结果:

执行用时 :1 ms, 在所有 Java 提交中击败了99.90%的用户
内存消耗 :38 MB, 在所有 Java 提交中击败了88.21%的用户

时间复杂度:O(n)

6.(2019.7.1)【70】假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。

  1. 1 阶 + 1 阶
  2. 2 阶

示例 2:

输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。

  1. 1 阶 + 1 阶 + 1 阶
  2. 1 阶 + 2 阶
  3. 2 阶 + 1 阶
  • 算法思想

1.暴力法

递归的最基本的方法。递归退出条件:还有一级台阶时只有一种方法,没有台阶时(也可以认为是从走了两级台阶上来的)有1种方法。递归表达式:剩余的台阶,假设走一步所产生的方法+假设走两步所产生的方法。

public int climbStairs(int n) {
        if(n==1||n==0)
            return 1;
        return climbStairs(n-1)+climbStairs(n-2);
}

结果:

超出时间限制

时间复杂度:O(2^n),树形递归

空间复杂度:递归所需要的栈,空间大小为O(n)

2.暴力法记忆优化

暴力法种,有许多的计算是重复的,我们将计算的结果放在一个数组种,假设若数组中不含元素,则计算并更新,若数组该位置中有元素,则直接取用。

    public int[] re=new int[1000];
    public int climbStairs(int n) {
        if(n==1||n==0)
            return 1;
        int num1;
        int num2;
        if(re[n-1]==0)
            num1=climbStairs(n-1);
        else
            num1=re[n-1];
        if(re[n-2]==0)
            num2=climbStairs(n-2);
        else
            num2=re[n-2];
        return num1+num2;
    }

结果

超出时间限制

时间复杂度:O(n),树形递归的大小可以达到 n

空间复杂度:O(n),递归树的深度可以达到 n

3.动态规划

这个问题可以被分解为一些包含最优子结构的子问题,即它的最优解可以从其子问题的最优解来有效地构建,我们可以使用动态规划来解决这一问题。 第 i阶可以由以下两种方法得到:

在第 (i−1) 阶后向上爬1阶。

在第 (i−2) 阶后向上爬2阶。

所以到达第 i 阶的方法总数就是到第 (i−1) 阶和第 (i−2)阶的方法数之和。 令 dp[i]表示能到达第 i阶的方法总数: dp[i]=dp[i−1]+dp[i−2]

其实这是一个斐波拉契数列。

public int climbStairs(int n) {
        if(n==1)
            return 1;
        int[] re=new int[n+1];
       re[1]=1;
       re[2]=2;
       for(int i=3;i<=n;i++){
           re[i]=re[i-1]+re[i-2];
       }
       return re[n];
}

时间复杂度:O(n),单循环到 n 。

空间复杂度:O(n),dp数组用了 n 的空间。