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 阶
- 2 阶
示例 2:
输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 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 的空间。