目录
leetcode(Java版本)
10.正则表达式的匹配(10表示在leetcode中是第10题以下不重复说明)
44.通配符匹配
3.无重复字符的最长子串
2.两个数相加
43.字符串相乘
1.两数相加
15.三数之和
16.最接近的三数之和
18.四数之和
5.最长回文子串
23.合并K个有序链表(分治思想)
32.最长有效括号(动态规划、类似于回文字符串)
204.计算质数
4.寻找两个有序数组的中位数
34.在排序数组中查找元素的开始和结束位置
153.寻找旋转排序数组中的最小值
81.搜索旋转排序数组中的最小值2
64.最小路径和(经典动态规划问题)
94.不同的二叉搜索树
95.不同的二叉搜索树2
94.二叉树的中序遍历
解法:1:回溯法采用暴力递归(时间复杂度和空间复杂度都不是很好),自顶向下的求解思路
class Solution {
public boolean isMatch(String s, String p) {
//条件是判断p是否为空,这是递归终止条件,当p是空串时就可以停止递归并且返回。判断s是否为空不可以。
if(p.isEmpty()){
return s.isEmpty();
}
//s一旦为空,就没有继续判断的必要,而上一个条件句已经保证不空。
boolean first_match = (!s.isEmpty() && (s.charAt(0) == p.charAt(0) || p.charAt(0) == '.'));
//需增加p的长度是否大于1的判断,因为有p.charAt(1)。下面是递归比较好理解。
if(p.length() >= 2 && p.charAt(1) == '*'){
return isMatch(s,p.substring(2)) || (first_match && isMatch(s.substring(1),p));
}
else
return first_match && isMatch(s.substring(1),p.substring(1));
}
}
解法2:备忘录递归,自顶向下的求解思路,去掉了递归重复计算的部分,用一个容器装(可以是map,也可以用数组实现)已经计算出来的部分,还有答案上采用自底向上的备忘录递归,感觉不如这个好理解有兴趣自己看。
enum Result{
TRUE,FALSE
//TRUE,FALSE分别表示s串第1个字符到第i+1个字符与p串第1个字符到第j+1个字符已经完成了匹配与否
//不使用boolean数组是因为枚举更简便地表示未开始匹配的状态(null)
}
class Solution {
Result[][] memo;
public boolean isMatch(String s, String p) {
//创建二维数组,作为备忘录
//因为在i和j都变成s.length()-1,p.length()-1时,p.length() > j+1不会满足,故程序不会执行第27行而会执行第29行
//因此二维数组被定义为new Result[s.length()+1][p.length()+1];而不是new Result[s.length()][p.length()]
memo = new Result[s.length()+1][p.length()+1];
//变量i,j表示s从s[i],p从p[j]开始匹配
return dp(0,0,s,p);
}
public boolean dp(int i,int j,String s,String p){
//如果s串从s[i]与p串从p[j]已经完成了匹配,那么直接返回(递归返回条件)
if(memo[i][j] != null){
return memo[i][j] == Result.TRUE;
}
//result简洁地表示本次memo[i][j]
boolean result;
if(j == p.length()){
result = i == s.length();
}
else{
boolean first_match = (i < s.length() && (p.charAt(j) == s.charAt(i) || p.charAt(j) == '.'));
if(p.length() > j+1 && p.charAt(j+1) == '*'){
result = dp(i,j+2,s,p) || (first_match && dp(i+1,j,s,p));
}else{
result = first_match && dp(i+1,j+1,s,p);
}
}
//如果memo[i][j]为空,那么可以通过上述代码用递归的方式求得memo[i][j]
memo[i][j] = result ? Result.TRUE:Result.FALSE;
//返回最终结果result
return result;
}
}
解法3:动态规划,自顶向下的解题思路。关键是得出转移方程,即已知dp[i][j](表示s的前i个与p的前j个能否匹配):
1.当s[i]=p[j]的时候,dp[i][j] = dp[i-1][j-1];
2.当p[j]='.'的时候,同上;
3.当p[j]='*'的时候,'*'表示前面的字符可以出现0次及其以上,可以分为以下几种情况:
3.1 当p[j-1]不等于s[i]时,dp[i][j] = dp[i][j-2]
3.2当p[j-1]等于'.'时或者p[j-1]等于s[i],是
dp[i][j] = dp[i-1][j] // 多个字符匹配的情况 (假设p[j-1]重复两次以上,因此回溯i游标)
or dp[i][j] = dp[i][j-1] // 单个字符匹配的情况(假设p[j-1]重复一次)
or dp[i][j] = dp[i][j-2] // 没有匹配的情况(假设p[j-1]重复零次)
class Solution {
public boolean isMatch(String s,String p){
if (s == null || p == null) {
return false;
}
boolean[][] dp = new boolean[s.length() + 1][p.length() + 1];
dp[0][0] = true;//dp[i][j] 表示 s 的前 i 个是否能被 p 的前 j 个匹配
for (int i = 0; i < p.length(); i++) { // here's the p's length, not s's
if (p.charAt(i) == '*' && dp[0][i - 1]) {
dp[0][i + 1] = true; // here's y axis should be i+1
}
}
for (int i = 0; i < s.length(); i++) {
for (int j = 0; j < p.length(); j++) {
if (p.charAt(j) == '.' || p.charAt(j) == s.charAt(i)) {//如果是任意元素 或者是对于元素匹配
dp[i + 1][j + 1] = dp[i][j];
}
if (p.charAt(j) == '*') {
if (p.charAt(j - 1) != s.charAt(i) && p.charAt(j - 1) != '.') {//如果前一个元素不匹配 且不为任意元素
dp[i + 1][j + 1] = dp[i + 1][j - 1];
} else {
dp[i + 1][j + 1] = (dp[i + 1][j] || dp[i][j + 1] || dp[i + 1][j - 1]);
/*
dp[i][j] = dp[i-1][j] // 多个字符匹配的情况
or dp[i][j] = dp[i][j-1] // 单个字符匹配的情况
or dp[i][j] = dp[i][j-2] // 没有匹配的情况
*/
}
}
}
}
return dp[s.length()][p.length()];
}
}
解法1:优化的滑动窗口(java的HashMap实现)
class Solution{
//改良滑动窗口法
public int lengthOfLongestSubstring(String s){
if(s == null){
return 0;
}
//定义字符到索引的映射,避免简单滑动窗口(left<=x map = new HashMap();
//max表示s串当前循环最大的无重复子串的长度,left表示指向“滑动窗口”的最左端
int max = 0;
int left = 0;
for(int i = 0;i < s.length();i ++){
if(map.containsKey(s.charAt(i))){
//这一步比较关键,[left,i)之间若s[i]在上述区间之间重复,只需让left变成max(left,map.get(s.charAt(i)) + 1)
//选用第16行而不是第17行的原因是left游标不能回溯,简单滑动窗口由于区间上下游标增1移动故不可能回溯
//因此增加Math.max(left,map.get(s.charAt(i)) + 1);保证了left游标不可能回溯
left = Math.max(left,map.get(s.charAt(i)) + 1);
//left = map.get(s.charAt(i)) + 1;
}
//这一步其实不管是否map的关键字中是否有s.charAt(i),s.charAt(i)和i都得被添加到map中
//因此没有else
map.put(s.charAt(i),i);
//每一次循环更新max的值
max = Math.max(max,i-left+1);
}
return max;
}
}
解法2:用int []来实现HashMap的功能
class Solution{
//改良滑动窗口法
public int lengthOfLongestSubstring(String s){
if(s == null){
return 0;
}
//定义字符到索引的映射,避免简单滑动窗口(left<=x map = new HashMap();
int[] index = new int[128];
//max表示s串当前循环最大的无重复子串的长度,left表示指向“滑动窗口”的最左端
int max = 0;
int left = 0;
for(int i = 0;i < s.length();i ++){
/*
if(map.containsKey(s.charAt(i))){
//这一步比较关键,[left,i)之间若s[i]在上述区间之间重复,只需让left变成max(left,map.get(s.charAt(i)) + 1)
//选用第16行而不是第17行的原因是left游标不能回溯,简单滑动窗口由于区间上下游标增1移动故不可能回溯
//因此增加Math.max(left,map.get(s.charAt(i)) + 1);保证了left游标不可能回溯
left = Math.max(left,map.get(s.charAt(i)) + 1);
//left = map.get(s.charAt(i)) + 1;
}
*/
left = Math.max(left,index[s.charAt(i)]);
index[s.charAt(i)] = i+1;
//这一步其实不管是否map的关键字中是否有s.charAt(i),s.charAt(i)和i都得被添加到map中
//因此没有else
//map.put(s.charAt(i),i);
//存入i+1避免24行代码无法判断是否已经存储
//index[s.charAt(i)] = i+1;
//每一次循环更新max的值
max = Math.max(max,i-left+1);
}
return max;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }//单链表节点的构造方法没有next的初始化
* }
*/
//默认链表存在头指针
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
//初始化目标节点的时候没有赋予next的值
ListNode result_node = new ListNode(0);
ListNode p = l1,q = l2,cuur = result_node;
int carry = 0;
//下面的条件由于是 || 的关系因此两个链表会一直被遍历,短的链表相应的位置补零
while(q != null || p != null){
//以下两行代码保证了长度不够的链表,在相应的位置上补零,以便按照最长的链表继续遍历下去
int x = (p == null) ? 0 : p.val;
int y = (q == null) ? 0 : q.val;
//下面一行代码充分体现了result_node是指向目标链表的头结点的
cuur.next = new ListNode((x + y + carry) % 10);
carry = (x + y + carry) / 10;
if(p != null)
p = p.next;
if(q != null)
q = q.next;
cuur = cuur.next;
}
if(carry != 0){
cuur.next = new ListNode(carry);
cuur.next.next = null;
}
return result_node.next;
}
}
那么,经过这一道题,可以得到遍历单链表的一个诀窍(就是想象这两个单链表的长度相等,那么较短的单链表的伪节点值域就是0)
解法:需要注意的是java语言获得数组、字符串、集合长度的方法不同:
数组:array.length;(此处应该注意,length与length()的区别)
字符串:string.length();
集合:list.size();
class Solution {
public String multiply(String num1, String num2) {
//String.equals()而不是"=="
if(num1.equals("0") || num2.equals("0")){
return "0";
}
//两个数相乘,最大的位数是这两个相乘的位数之和
int[] result = new int[num1.length() + num2.length()];
//String类型的对象的值是不可改变的,每次对String的操作都会生成新的String对象。
//因此使用StringBuilder类型的对象
StringBuilder str = new StringBuilder();
for(int i = num1.length()-1;i >= 0;i --){
int n1 = num1.charAt(i) - '0';
for(int j = num2.length() - 1;j >= 0;j --){
//'0'的阿西克码值是48
int n2 = num2.charAt(j) - 48;
//n1*n2的结果,可以看成一个两位数(0x or xy),那么十位被存在result[i + j],个位被存储在result[i + j + 1]
//num1[i] * num2[j]的结果是二者相乘的结果加上上一次相乘(很多种情况)的进位result[i+j+1]
int sum = result[i+j+1] + n1 * n2;
//更新result[i + j + 1]
result[i + j + 1] = sum % 10;
//可以理解为num1[i] * num2[j]的进位进到result[i + j],因此是"+="
result[i + j] += sum / 10;
}
}
for(int k = 0;k < result.length;k ++){
// 结果最大的位数是两数相加,最小的位数是两数相加减去1
if(k == 0 && result[k] == 0)
continue;
str.append(result[k]);
}
return str.toString();
}
}
解法1(两遍哈希表:因为需要用到两次迭代):定义一个HashMap,有效避免了二重循环的问题。
判断一维数组(A)是否为空:A.length == 0 or A == null
判断二维数组(A)是否为空:第一行的数组长度是否为空,即A[0].length == 0,考虑{},{{}},{0}
class Solution {
public int[] twoSum(int[] nums, int target) {
if(nums == null){
return null;
}
//也可以换成下面一句
//throw new IllegalArgumentException("所给数组不符合要求")
//HashMap map = new HashMap();
HashMap map = new HashMap<>();
int i = 0;
for(i = 0;i < nums.length;i ++){
map.put(nums[i],i);
}
//HashMap的关键字集合中首先得含有target - nums[i],其次两者的下标不能相等
for(i = 0;i < nums.length;i ++){
if(map.containsKey(target - nums[i]) && map.get(target - nums[i]) != i){
return new int[]{ i , map.get(target - nums[i])};
}
}
return null;
}
}
解法2:在构建哈希表的时候就判断是否满足条件。
class Solution {
public int[] twoSum(int[] nums, int target) {
Map map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[] { map.get(complement), i };
}
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
}
解法:双指针问题,值得注意的是:List中,为null和isEmpty()是两个概念,为空意味着没有对对象分配内存空间,而后者表示这个集合没有元素,但是分配了内存空间,创建了空集合。
class Solution {
public List> threeSum(int[] nums) {
List> ans = new ArrayList<>();
Arrays.sort(nums);
int k = 0,i,j = nums.length;
for(k = 0;k < nums.length;k ++){
if(nums[k] > 0)
break;
if(k > 0 && nums[k] == nums[k-1])
continue;
i = k + 1;
j = nums.length - 1;
while(i(Arrays.asList(nums[k],nums[i],nums[j])));
while(i < j && nums[i] == nums[++ i]);
while(i < j && nums[j] == nums[-- j]);
}else if(sum < 0){
while(i < j && nums[i] == nums[++ i]);
}else{
while(i < j && nums[j] == nums[-- j]);
}
}
}
return ans;
}
}
解法:简单的双指针的解法,当差值为零时,直接返回。时间复杂度和空间复杂度都是中等。排序法加上双指针就是这类问题的统一解法。
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int ans = nums[0] + nums[1] + nums[2];
int value = Math.abs(target - ans);
int k;
int i,j;
for(k = 0;k < nums.length - 1;k ++){
i = k + 1;
j = nums.length - 1;
/*
if(k > 0 && nums[k] == nums[k - 1])
continue;
*/
while(i < j){
if(value == 0){
return ans;
}
if(value > Math.abs(nums[k] + nums[i] + nums[j] - target)){
ans = nums[k] + nums[i] + nums[j];
value = Math.abs(nums[k] + nums[i] + nums[j] - target);
}
if(nums[k] + nums[i] + nums[j] > target){
while(i < j && nums[j] == nums[-- j]);
}
else if(nums[k] + nums[i] + nums[j] < target){
while(i < j && nums[i] == nums[++ i]);
}
}
}
return ans;
}
}
class Solution {
public List> fourSum(int[] nums, int target) {
List> result = new ArrayList<>();
Arrays.sort(nums);
if(nums.length < 4 || nums == null){
return result;
}
int k,p,i,j;
int length = nums.length;
for(p = 0;p < nums.length - 3;p ++){
if(p > 0 && nums[p] == nums[p-1])
continue;
int min1 = nums[p] + nums[p + 1] +nums[p + 2] + nums[p + 3];
int max1 = nums[p] + nums[length - 1] +nums[length - 2] + nums[length - 3];
if(min1 > target || max1 < target)
continue;
for(k = p + 1;k < nums.length - 2;k ++){
if(k > p+1 && nums[k] == nums[k - 1])
continue;
int min2 = nums[p] + nums[k] +nums[k + 1] + nums[k + 2];
int max2 = nums[k] + nums[p] +nums[length - 1] + nums[length - 2];
if(min2 > target || max2 < target)
continue;
i = k + 1;
j = nums.length - 1;
while(i < j){
int sum = nums[p] + nums[k] + nums[i] +nums[j];
if(sum == target){
ArrayList tmp = new ArrayList(Arrays.asList(nums[p],
nums[k],
nums[i],
nums[j]));
result.add(tmp);
while(i < j && nums[i] == nums[++ i]);
while(i < j && nums[j] == nums[-- j]);
}
else if(sum < target){
while(i < j && nums[i] == nums[++ i]);
}else{
while(i < j && nums[j] == nums[-- j]);
}
}
}
}
return result;
}
}
解法1:回溯法:s[l][r]表示s中从s[l]到s[r]是回文字符串那么可以得到以下状态转移方程:
if(s.charAt(r) == s.charAt(l) && (r - l <= 2 || dp[l + 1][r - 1])){
dp[l][r] = true;
}
这种方法的时间复杂度和空间复杂度都不是很好:
执行用时 :48 ms, 在所有 java 提交中击败了52.01% 的用户
内存消耗 :39.3 MB, 在所有 java 提交中击败了60.36%的用户
class Solution {
public String longestPalindrome(String s) {
int length = s.length();
if(length <= 1){
return s;
}
int longgest = 1;
//String ans = null;
//substring()方法而不是S大写
String ans = s.substring(0, 1);
boolean[][] dp = new boolean[length][length];
//r指向待判断是否是回文子串的右边,而ll指向左边,因此r从1开始遍历,l从0开始并且l < r
for(int r = 1;r < length;r ++){
for(int l = 0;l < r;l ++){
//r - l <= 2表示dp[l+1][r-1]中要么有0个元素要么有1个元素,这两种情况都不用判断
if(s.charAt(r) == s.charAt(l) && (r - l <= 2 || dp[l + 1][r - 1])){
dp[l][r] = true;
if(r - l +1 > longgest){
longgest = r - l + 1;
ans = s.substring(l,r + 1);
}
}
}
}
return ans;
}
}
解法2:Manacher's Algorithm 马拉车算法:时间复杂度和空间复杂度都可以接受
class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if(len < 2){
return s;
}
String new_str = addBoundary(s, '#');
int new_len = 2 * len + 1;
int max_len = 1;
int start = 0;
for(int i = 0;i < new_len;i ++){
int cur_len = centerSpread(new_str,i);
if(cur_len > max_len){
max_len = cur_len;
start = (i - max_len) / 2;
}
}
return s.substring(start,start + max_len);
}
public String addBoundary(String s,char div){
int len = s.length();
if(len == 0){
return "";
}
if(s.indexOf(div) != -1){
throw new IllegalArgumentException("参数错误");
}
StringBuilder str = new StringBuilder();
for(int i = 0;i < len;i ++){
str.append(div);
str.append(s.charAt(i));
}
str.append(div);
return str.toString();
}
private int centerSpread(String s,int center){
int len = s.length();
int i = center - 1;
int j = center + 1;
int step = 0;
while(i >= 0 && j < len && s.charAt(i) == s.charAt(j)){
-- i;
++ j;
++ step;
}
return step;
}
}
解法1:分治思想
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists.length == 0){
return null;
}else{
return solve(lists,0,lists.length - 1);
}
}
private ListNode solve(ListNode[] lists,int first,int second){
if(first == second){
return lists[first];
}
//以下体现分治思想
int mid = (first + second) >> 1;
ListNode node1 = solve(lists,first,mid);
ListNode node2 = solve(lists,mid + 1,second);
return merge2Listnodes(node1,node2);
}
private ListNode merge2Listnodes(ListNode first,ListNode second){
//这是归并两个ListNode的算法
//这两个链表有一个为空,那么就返回对方
if(first == null || second == null){
return first == null ? second : first;
}
if(first.val < second.val){
first.next = merge2Listnodes(first.next,second);
return first;
}else{
second.next = merge2Listnodes(first,second.next);
return second;
}
}
}
解法1:动态规划,不建议使用动态规划,太过与复杂。
class Solution {
public int longestValidParentheses(String s) {
int maxlen = 0;
//dp[i]代表以s[i]结尾的字符串的满足要求的长度
int[] dp = new int[s.length()];
//dp[0] = 0;
for(int i = 1;i < s.length();i ++){
if(s.charAt(i) == ')'){
if(s.charAt(i - 1) == '('){
/*
if(i >= 2){
dp[i] = dp[i - 2] + 2;
}else{
dp[i] = 2;
}
*/
dp[i] = ((i >= 2) ? dp[i - 2]:0) + 2;
}
//(i - dp[i - 1] >= 1)这个条件应该在前面,如果在后面会报错
else if( (i - dp[i - 1] >= 1) && s.charAt(i - dp[i - 1] - 1) == '(' ){
dp[i] = dp[i - 1] + (i - dp[i - 1] >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
}
maxlen = Math.max(maxlen,dp[i]);
}
}
return maxlen;
}
}
解法2:计数器法
class Solution {
public int longestValidParentheses(String s) {
int left = 0,right = 0;
int maxlen = 0;
for(int i = 0;i < s.length();i ++){
if(s.charAt(i) == '(')
left ++;
else{
right ++;
}
if(left == right)
maxlen = Math.max(maxlen,2 * right);
else if(right > left){
left = 0;
right = 0;
}
}
left = 0;
right = 0;
for(int i = s.length() - 1;i >= 0;i --){
if(s.charAt(i) == '(')
left ++;
else
right ++;
if(left == right)
maxlen = Math.max(maxlen,2 * right);
else if(left > right){
left = 0;
right = 0;
}
}
return maxlen;
}
}
解法1:质数打表法:比较适合n较大的情况,因此在这数较小的情况下时间复杂度和空间复杂度并不好
class Solution {
public int countPrimes(int n) {
return table(n);
}
private int table(int n){
if(n < 3){
return 0;
}
int[] prime = new int[n];
boolean[] visit = new boolean[n];
//为了表达简易,这里面false代表是质数
visit[0] = visit[1] = true;
int num = 0;
for(int i = 2;i < n;i ++){
if(visit[i] == false){
num ++;
prime[num] = i;
}
for(int j = 1;(j <= num && i * prime[j] < n);j ++){
visit[i * prime[j]] = true;
if(i % prime[j] == 0){
break;
}
}
}
int sum = 0;
for(int k = 0;k < n;k ++){
if(visit[k] == false){
sum ++;
}
}
return sum;
}
}
解法:由于题目要求的是时间复杂度是O(log(m + n)),因此很自然想到借用二分查找与递归结合进行解题
https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n = nums1.length;
int m = nums2.length;
//无论m + n 是奇数还是偶数,中位数是第left个数和第right个数之和
int left = (m + n + 1) / 2;
int right = (m + n + 2) / 2;
//很尴尬,* 0.5 可以
return (getKth(nums1,0,n - 1,nums2,0,m - 1,left) + getKth(nums1,0,n - 1,nums2,0,m - 1,right)) * 0.5;
}
//这个函数是为了在nums1和nums2中找出第K小的数
private int getKth(int[] nums1,int start1,int end1,int[] nums2,int start2,int end2,int k){
int length1 = end1 - start1 + 1;
int length2 = end2 - start2 + 1;
//让length1的长度小于length2,如果有一个数组空了,那么一定是length1
if(length1 > length2)
return getKth(nums2,start2,end2,nums1,start1,end1,k);
/*
try{
if(length1 == 0)
return nums2[start2 + k - 1];
}catch(Exception e){
}
*/
if(length1 == 0)
return nums2[start2 + k - 1];
//递归结束条件
if(k == 1){
return Math.min(nums2[start2],nums1[start1]);
}
//此处体现出来二分查找的特点
int i = start1 + Math.min(length1,k / 2) - 1;
int j = start2 + Math.min(length2,k / 2) - 1;
//小于等于和小于的结果一样
if(nums1[i] < nums2[j]){
return getKth(nums1,i + 1,end1,nums2,start2,end2,k - (i - start1 + 1));
}else{
return getKth(nums1,start1,end1,nums2,j + 1,end2,k - (j - start2 + 1));
}
}
}
解法1:利用哈希表,但是速度很慢,题目要求了时间复杂度
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] ans = new int[]{-1,-1};
if(nums.length == 0){
return ans;
}
HashMap map_left = new HashMap<>();
HashMap map_right = new HashMap<>();
int left = 0;
int right = 0;
//主要利用哈希表的覆盖原理,从左向右和从右向左扫描
for(int i = 0;i < nums.length;i ++){
map_left.put(nums[i],i);
}
for(int j = nums.length - 1;j > -1;j --){
map_right.put(nums[j],j);
}
//可能左右相等
if(map_left.containsKey(target)){
left = map_right.get(target);
right = map_left.get(target);
ans[0] = left;
ans[1] = right;
}
return ans;
}
解法2:二分查找(因为题目要求的时间复杂度是O(logn)),分析二分查找的一个技巧是:不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节。计算mid的时候为了防止溢出,可以采用 mid=left+(right-left)/2
详见https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array/solution/er-fen-cha-zhao-suan-fa-xi-jie-xiang-jie-by-labula/
class Solution {
public int[] searchRange(int[] nums, int target) {
int[] ans = {-1,-1};
int left_index = search_bound(nums,target,true);
if(left_index == nums.length || nums[left_index] != target)
return ans;
int right_index = search_bound(nums,target,false) - 1;
ans[0] = left_index;
ans[1] = right_index;
return ans;
}
//flag表明是否是左边界查找
private int search_bound(int[] nums,int target,boolean flag){
int left = 0;
int right = nums.length;
while(left < right){
int mid = (left + right) / 2;
if(nums[mid] > target || (flag && target == nums[mid]))
right = mid;
/*
else if(nums[mid] < target){
left = mid + 1;
}
*/
else
left = mid + 1;
}
return left;
}
}
上面这一种也可以,就是空间复杂度低一点,但是不能有两个else if那样leetcode的时间复杂度会不满足要求。
解法1:时间复杂度和空间复杂度都不是很好,这个数组可能会发生旋转没有重复值
class Solution {
public int findMin(int[] nums) {
Arrays.sort(nums);
return nums[0];
}
}
解法2:二分查找:
class Solution {
public int findMin(int[] nums) {
if(nums.length == 0)
throw new IllegalArgumentException("所给的数组有误");
if(nums.length == 1)
return nums[0];
if(nums.length == 2){
return Math.min(nums[0],nums[1]);
}
int left = 0;
int right = nums.length - 1;
while(left < right){
//以nums[nums.length - 1]为哨兵
int mid = left + (right - left) / 2;
if(nums[mid] > nums[mid + 1] && mid + 1 < nums.length - 1)
return nums[mid + 1];
if( mid - 1 > 0 && nums[mid - 1] > nums[mid])
return nums[mid];
//绘制一个分段函数可以解释left和right的变化
/*
if(nums[mid] > nums[right]){
left = mid + 1;
}else if(nums[mid] < nums[right]){
right = mid;
}
*/
//以nums[0]为哨兵,不可行,因为题目上是可能进行旋转,故不适合
//当数组没有进行旋转的时候不通过
if(nums[mid] < nums[0]){
left = mid + 1;
}else{
right = mid;
}
}
//返回left和right没有区别
return nums[left];
}
}
解法1:这个数组可能会旋转,另外这个数组存在重复元素,使用HashSet去重,时间复杂度和空间复杂度很差
class Solution {
public int findMin(int[] nums) {
if(nums == null){
return -1;
}
HashSet set = new HashSet<>();
int min = nums[0];
for(int i = 0;i < nums.length;i ++){
set.add(nums[i]);
}
for(int value : set){
if(value < min)
min = value;
}
return min;
}
}
解法2:因为数组基本有序因此使用二分查找来进行解题,这个解法也适用于153题的解法
class Solution {
public int findMin(int[] nums) {
//这个数组nums可以分为两个非递减的数组nums1和nums2
//以下不能写成Exception,不然会报错
if(nums.length == 0)
throw new IllegalArgumentException("所给的数组为空!");
if(nums.length == 1)
return nums[0];
int left = 0;
int right = nums.length - 1;
while (left < right) {
int mid = left + (right - left) / 2;
//nums[mid] > nums[right],证明最小值在nums2中,
//并且一定不是nums[mid],因此令乐left = mid + 1
if(nums[mid] > nums[right]){
left = mid + 1;
}else if(nums[mid] < nums[right]){
//如果nums[mid] < nums[right],那么最小值一定在nums1中,但是有可能是nums[mid]
//因此令right = mid
right = mid;
}else if(nums[mid] == nums[right]){
//nums[mid]既可能在nums1中,又可能在nums2中
//故令right = right - 1最稳妥
right --;
}
}
return nums[left];
}
}
该数组一定会发生旋转,有重复值
解法1:先用Arrays.sort()进行排序,然后再用普通的二分查找进行寻找
class Solution {
public boolean search(int[] nums, int target) {
//首先这个题与153题的区别是首先数组一定会旋转,其次这个数组有重复元素
//时间复杂度为O(Nlog(N))
if(nums.length == 0)
return false;
if(nums.length == 1 && nums[0] == target)
return true;
Arrays.sort(nums);
int left = 0;
int right = nums.length - 1;
while(left < right){
int mid = left + (right - left) / 2;
if(nums[mid] == target){
return true;
}else if(nums[mid] > target){
right = mid - 1;
}else
left = mid + 1;
}
if(nums[left] == target)
return true;
return false;
}
}
解法2:暴力递归,时间复杂度很好,但是空间复杂度很差
class Solution {
public boolean search(int[] nums, int target) {
if(nums.length == 0)
return false;
boolean ans = binary_search(nums,target,0,nums.length - 1);
return ans;
}
private boolean binary_search(int[] nums,int target,int left,int right){
if(left == right)
return (nums[left] == target);
if(left > right){
return false;
}
while(left < right){
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return true;
return binary_search(nums,target,mid + 1,right) || binary_search(nums,target,left,mid - 1);
}
return (nums[left] == target);
}
}
解法3:因为数组基本有序,采用二分查找
class Solution {
public boolean search(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return true;
if (nums[left] == nums[mid] && nums[mid] == nums[right]) {
left++;
right--;
} else if (nums[left] <= nums[mid]) {
if (nums[left] <= target && target < nums[mid]) right = mid - 1;
else left = mid + 1;
} else {
if (nums[mid] < target && target <= nums[right]) left = mid + 1;
else right = mid - 1;
}
}
return false;
}
}
解法:二维动态规划,时间复杂度和空间复杂度都很好,也容易想到
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
//这个地方容易搞错,是m而不是m - 1
int[][] dp = new int[m][n];
//dp[i][j]表示到grid[i][j]的最小路径长度
dp[0][0] = grid[0][0];
int i = 1;
int j = 1;
//把第一行和第一列初始化,因为到他们的选择唯一
for(i = 1;i < m;i ++){
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for(j = 1;j < n;j ++){
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
//循环完成dp[][]的赋值
for(i = 1;i < m;i ++){
for(j = 1;j < n;j ++){
if(i == 0)
dp[i][j] = dp[i][j - 1] + grid[i][j];
else if(j == 0)
dp[i][j] = dp[i - 1][j] + grid[i][j];
else
dp[i][j] = Math.min(dp[i - 1][j],dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1];
}
}
class Solution {
public int minPathSum(int[][] grid) {
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
if(i == 0 && j == 0) continue;
else if(i == 0) grid[i][j] = grid[i][j - 1] + grid[i][j];
else if(j == 0) grid[i][j] = grid[i - 1][j] + grid[i][j];
else grid[i][j] = Math.min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j];
}
}
return grid[grid.length - 1][grid[0].length - 1];
}
}
以上是优化版本,时间复杂度和空间复杂度都差不多。
解法:动态规划,与树有关的动态规划要注意树根对解题思路的影响
class Solution {
public int numTrees(int n) {
//一维的动态规划问题
//卡特兰数,二叉搜索树问题,这类问题不像之前的一维的动态规划问题,思路比较新颖
//dp[0]-dp[n],因此是n + 1个
if(n <= 1){
return 1;
}
int[] dp = new int[n+1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2;i <= n;i ++){
for(int j = 1;j <= i;j ++){
//注意是+=,j相当于在遍历树根所在的位置
dp[i] += dp[j - 1]*dp[i - j];
}
}
return dp[n];
}
}
解法:递归的方法,二叉搜索树的根节点是1...n,因此当可能的根节点选择在其间时,自然兼顾了二叉搜索树的特点
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List generateTrees(int n) {
List ans = new ArrayList<> ();
if(n == 0)
return ans;
return getAns(1,n);
}
private List getAns(int start,int end){
List ans = new ArrayList<> ();
//以下两个if条件是递归结束条件
if(start == end){
//如果是return ans.add(new TreeNode(start));不正确
//原因是add()函数的返回值是boolean类型的
//TreeNode tree = new TreeNode(start);
ans.add(new TreeNode(start));
return ans;
}
if(start > end){
ans.add(null);
return ans;
}
//尝试将i循环作为根节点,然后进行递归,
//每一个根节点进行左右子树递归之后,需要将这个根节点的左子树和右子树添加到这个根节点上
for(int i = start;i <= end;i ++){
//犯的错误很浅显
//List left_trees = getAns(1,i - 1);
List left_trees = getAns(start,i - 1);
List right_trees = getAns(i + 1,end);
//以下两种都是可以接受的
//但是必须是嵌套循环,道理很简单根节点的编号是i,左子树集合和右子树集合是乘法关系(第94题)
/*
for(TreeNode left_tree : left_trees){
for(TreeNode right_tree : right_trees){
TreeNode root_i = new TreeNode(i);
root_i.left = left_tree;
root_i.right = right_tree;
ans.add(root_i);
}
}
*/
for(TreeNode left_tree : right_trees){
for(TreeNode right_tree : left_trees){
TreeNode root_i = new TreeNode(i);
root_i.right = left_tree;
root_i.left = right_tree;
ans.add(root_i);
}
}
}
return ans;
}
}
解法1:递归
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List inorderTraversal(TreeNode root) {
List ans = new ArrayList<> ();
helper(root,ans);
return ans;
}
private void helper(TreeNode root,List ans){
if(root != null){
if(root.left != null)
helper(root.left,ans);
ans.add(root.val);
if(root.right != null)
helper(root.right,ans);
}
}
}
解法2:利用栈进行非递归遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List inorderTraversal(TreeNode root) {
List ans = new ArrayList<> ();
TreeNode tree = root;
Stack stack = new Stack<>();
while(tree != null || !stack.empty()){
/*
while(tree.left != null){
stack.push(tree);
tree = tree.left;
}
*/
//此处不应该是tree.left != null,因为tree先入的栈
while(tree != null){
stack.push(tree);
tree = tree.left;
}
if( !stack.empty() ){
tree = stack.pop();
ans.add(tree.val);
tree = tree.right;
}
}
return ans;
}
}
解法3:莫里斯遍历:能在时间复杂度为·O(n),空间复杂度为O(1)的情况下完成树的前序、中序、后序遍历。结合线索化二叉树的知识。莫里斯算法完成树的前序、中序、后序遍历的详细解法如下链接:https://www.cnblogs.com/AnnieKim/archive/2013/06/15/morristraversal.html
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List inorderTraversal(TreeNode root) {
List ans = new ArrayList<> ();
TreeNode curr = root;
TreeNode pre = null;
while(curr != null){
if(curr.left == null){
ans.add(curr.val);
curr = curr.right;
}
else{
pre = curr.left;
while(pre.right != null && pre.right != curr)
pre = pre.right;
//如果还没有完成中序遍历线索化,那就就完成中序遍历线索化:pre.right = curr;
if(pre.right == null){
//完成中序遍历线索化
//让curr指针指向curr的左子树
pre.right = curr;
curr = curr.left;
}else if(pre.right == curr){
//如果已经完成了线索化,证明curr的左子树已经遍历完毕,
//那么就恢复树原来的结构,并且将访问curr节点之后访问curr的右子树
pre.right = null;
ans.add(curr.val);
curr = curr.right;
}
}
}
return ans;
}
}
莫里斯算法的中序遍历和前序遍历几乎相同,莫里斯遍历的后序遍历暂时不掌握,前序遍历如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List inorderTraversal(TreeNode root) {
List ans = new ArrayList<> ();
TreeNode curr = root;
TreeNode pre = null;
while(curr != null){
if(curr.left == null){
ans.add(curr.val);
curr = curr.right;
}
else{
pre = curr.left;
while(pre.right != null && pre.right != curr)
pre = pre.right;
//如果还没有完成中序遍历线索化,那就就完成中序遍历线索化:pre.right = curr;
if(pre.right == null){
//完成中序遍历线索化
//让curr指针指向curr的左子树
pre.right = curr;
//下面这一句是前序与中序的唯一区别
ans.add(curr.val);
curr = curr.left;
}else if(pre.right == curr){
//如果已经完成了线索化,证明curr的左子树已经遍历完毕,
//那么就恢复树原来的结构,并且将访问curr节点之后访问curr的右子树
pre.right = null;
//ans.add(curr.val);
curr = curr.right;
}
}
}
return ans;
}
}
解法1:简单的二维动态规划,时间复杂度和空间复杂度都不是很好,也可以用排列组合的方法求解
class Solution {
public int uniquePaths(int m, int n) {
//二维动态规划问题
if(m == 1 || n == 1)
return 1;
int[][] ans = new int[m][n];
ans[0][0] = 1;
for(int i = 0;i < m;i ++){
for(int j = 0;j < n;j ++){
if(i == 0 || j == 0){
ans[i][j] = 1;
}
}
}
for(int i = 1;i < m;i ++){
for(int j = 1;j < n;j ++){
ans[i][j] = ans[i][j - 1] + ans[i - 1][j];
}
}
return ans[m - 1][n - 1];
}
}
解法2:当做杨辉三角,用一维动态规划来求解
class Solution {
public int uniquePaths(int m, int n) {
//这是一个杨辉三角的问题,其中二维动态规划问题可以转化为一维动态规划的问题
int[] ans = new int [n];
Arrays.fill(ans,1);
for(int i = 1;i < m;i ++){
for(int j = 1;j < n;j ++){
ans[j] += ans[j - 1];
}
}
return ans[n - 1];
}
}
解法1:简单的二维动态规划问题,就是首先需要处理表示障碍的边界数组问题。时间复杂度可以接受,但是由于使用了二维数组导致空间复杂度太大。
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
if(obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1){
return 0;
}
int[][] ans = new int[m][n];
//首先改变obstacleGrid数组,让边界值等于1的后续边界处的值也等于1
for(int i = 1;i < m;i ++){
obstacleGrid[i][0] = (obstacleGrid[i][0] == 0 && obstacleGrid[i - 1][0] == 0) ? 0 : 1;
}
for(int i = 1;i < n;i ++){
obstacleGrid[0][i] = (obstacleGrid[0][i] == 0 && obstacleGrid[0][i - 1] == 0) ? 0 : 1;
}
for(int i = 0;i < m;i ++){
for(int j = 0;j < n;j ++){
if(i == 0 || j == 0){
ans[i][j] = 1 - obstacleGrid[i][j];
}else{
ans[i][j] = obstacleGrid[i][j] == 1 ? 0 : ans[i][j - 1] + ans[i - 1][j];
}
}
}
return ans[m - 1][n - 1];
}
}
解法1:用Arrays.sort()函数进行排序之后,用count统计正数的个数,时间复杂度和空间复杂度都很好
class Solution {
public int firstMissingPositive(int[] nums) {
if(nums.length == 0)
return 1;
Arrays.sort(nums);
if(nums[0] > 1 || nums[nums.length - 1] < 1)
return 1;
int count = 0;
count = (nums[0] > 0) ? 1 : 0;
for(int i = 1;i <= nums.length - 1;i ++){
if(nums[i] > 0 && nums[i] != nums[i - 1]){
count ++;
if(count < nums[i]){
return count;
}
}
}
return (nums[nums.length - 1] > 0) ? nums[nums.length - 1] + 1 : 1;
}
}
解法2:使用桶排序(鸽笼原理),难度不是很大
class Solution {
//通过桶排序,让数组中的x尽可能地在nums[x - 1]上
public int firstMissingPositive(int[] nums) {
int len = nums.length;
for(int i = 0;i < len;i ++){
//这个是while循环而不是if条件的原因是可能替换后,还得继续替换的原因
while(nums[i] > 0 && nums[i] < len + 1 && nums[nums[i] - 1] != nums[i]){
swap(nums,nums[i] - 1,i);
}
}
for(int i = 0;i < len;i ++){
if((i + 1) != nums[i]){
return i + 1;
}
}
return len + 1;
}
private void swap(int[] nums,int first,int second){
if(nums.length >= 2){
try{
int tem;
tem = nums[first];
nums[first] = nums[second];
nums[second] = tem;
}catch(Exception e){
System.out.println("数组越界了");
}
}
}
}
交换可以通过异或运算^来完成:实现原理https://blog.csdn.net/jocks/article/details/8182521
nums[index1] = nums[index1] ^ nums[index2];
nums[index2] = nums[index1] ^ nums[index2];
nums[index1] = nums[index1] ^ nums[index2];
解法:此解法是利用题目中没有重复数据并且数组肯定发生旋转,来寻找left-mid-right之间单调递增的区域,要考两个判断条件来寻找
class Solution {
//根据题意,这个数组一定发生了旋转,并且假设数组中存在不重复的元素
//时间复杂度有要求, O(log n)
//数组基本有序,因此使用二分查找
public int search(int[] nums, int target) {
//Arrays.sort(nums);
if(nums.length == 0 || (nums.length == 1 && nums[0] != target))
return -1;
int len = nums.length;
int left = 0;
int right = nums.length - 1;
while(left < right){
int mid = left + (right - left) / 2;
if(nums[mid] == target)
return mid;
/*
if(nums[mid] > nums[right]){
//nums[mid] > nums[right]表示left到mid之间递增,
//因此下面一个条件句判断如果target在left到mid之间
if(target >= nums[left] && target < nums[mid])
right = mid - 1;
else
left = mid + 1;
}else if(nums[mid] < nums[right]){
//else if和else效果一样
if(target > nums[mid] && target <= nums[right])
left = mid + 1;
else
right = mid - 1;
}
*/
//如果有两个元素,参考地板除法,因此这个地方应该使用大于等于
if(nums[mid] >= nums[left]){
if(target >= nums[left] && target < nums[mid])
right = mid - 1;
else
left = left + 1;
}else if(nums[mid] < nums[left]){
if(target > nums[mid] && target <= nums[right])
left = mid + 1;
else
right = mid - 1;
}
}
return (nums[left] == target) ? left : -1;
}
}
解法1:dp[i]表示以nums[i]结尾的连续子数组的和,那么转移方程可以如下程序所示
class Solution {
public int maxSubArray(int[] nums) {
//dp[i]表示以nums[i]结尾的子数组的和
int n = nums.length;
int ans = nums[0];
if(n == 1){
return ans;
}
int[] dp = new int[n];
dp[0] = nums[0];
for(int i = 1;i < n;i ++){
if(nums[i] < 0){
if(dp[i - 1] > 0)
dp[i] = nums[i] + dp[i - 1];
else{
dp[i] = nums[i];
}
}else{
if(dp[i - 1] < 0){
dp[i] = nums[i];
}else
dp[i] = dp[i - 1] + nums[i];
}
}
for(int j = 1;j < n;j ++){
if(dp[j] > ans){
ans = dp[j];
}
}
return ans;
}
}
解法2:从上面未精简的算法可以看出:当dp[i - 1] < 0时,dp[i] = nums[i]。
class Solution {
public int maxSubArray(int[] nums) {
//dp[i]表示以nums[i]结尾的子数组的和
//当dp[i - 1] < 0时,dp[i] = nums[i] + 0
//当dp[i - 1] >= 0时,dp[i] = dp[i - 1] + nums[i]
int ans = nums[0];
for(int i = 1;i < nums.length;i ++){
nums[i] += Math.max(nums[i - 1],0);
ans = Math.max(ans,nums[i]);
}
return ans;
}
}
解法:头插法,代码如下:
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null)
return head;
ListNode result = new ListNode(0);
result.next = null;
ListNode p = head;
ListNode tem;
while(p != null){
tem = p;
//注意:p = p.next;
//应该写到tem的next改变之前,因为tem和p指向同一个对象
p = p.next;
tem.next = result.next;
result.next = tem;
}
return result.next;
}
}
解法1:简单选择排序(时间复杂度和空间复杂度都很低)
class Solution {
public ListNode sortList(ListNode head) {
if(head == null || head.next == null)
return head;
ListNode ans = new ListNode(0);
ListNode current = head;
//引入after的原因是java传递对象相当于c的传引用,会导致current的next
//被修改
ListNode after = current.next;
while(current.next != null){
ans = sort(current,ans);
current = after;
after = after.next;
}
return sort(current,ans).next;
}
public ListNode sort(ListNode current,ListNode ans){
if(ans.next == null){
ans.next = current;
ans.next.next = null;
return ans;
}
ListNode pre = ans;
ListNode tem = ans.next;
while(tem != null){
if(tem.val >= current.val){
current.next = pre.next;
pre.next = current;
return ans;
}
pre = tem;
tem = tem.next;
}
current.next = pre.next;
pre.next = current;
return ans;
}
}
解法2:归并排序,空间复杂度不是很好
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode sortList(ListNode head) {
if(head == null || head.next == null)
return head;
ListNode ans = head;
ans = merge_sort(head);
return ans;
}
//使用归并排序对单链表进行排序
public ListNode merge_sort(ListNode ans){
if(ans == null || ans.next == null)
return ans;
ListNode[] listnodes = divide(ans);
ListNode left = merge_sort(listnodes[0]);
ListNode right = merge_sort(listnodes[1]);
return merge(left,right);
}
//合并两个有序单链表
public static ListNode merge(ListNode curr_left,ListNode curr_right){
ListNode ans = new ListNode(-1);
ListNode curr = ans;
while(curr_left != null && curr_right != null){
if(curr_left.val < curr_right.val){
curr.next = curr_left;
curr_left = curr_left.next;
}else{
curr.next = curr_right;
curr_right = curr_right.next;
}
curr = curr.next;
}
curr.next = curr_left == null ? curr_right : curr_left;
return ans.next;
}
public static ListNode[] divide(ListNode ans){
ListNode current = ans;
ListNode[] listnodes = new ListNode[2];
if(ans == null){
return listnodes;
}
ListNode ans1 = null;
ListNode ans2 = null;
int left =1;
int right = 0;
while(current != null){
right ++;
current = current.next;
}
current = ans;
int mid = (left + right) / 2;
while(current != null){
if(left == mid){
ans1 = ans;
ans2 = current.next;
current.next = null;
break;
}
current = current.next;
left ++;
}
/*
current = ans1;
while(current != null){
System.out.println(current.val);
current = current.next;
}
*/
listnodes[0] = ans1;
listnodes[1] = ans2;
return listnodes;
}
}
解法:如果是倒序,那么就用头插法,否则是尾插法。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode curr = new ListNode(0);
ListNode ans = curr;
while(l1 != null && l2 != null){
if(l1.val < l2.val){
ans.next = l1;
l1 = l1.next;
}else{
ans.next = l2;
l2 = l2.next;
}
ans = ans.next;
}
ans.next = l1 == null ? l2 : l1;
return curr.next;
}
}
解法:双指针,这是解决数组问题的基本思路
class Solution {
public boolean canThreePartsEqualSum(int[] A) {
if(A.length < 3){
return false;
}else{
int left = 0;
int right = A.length - 1;
int sum = 0;
for(int i = 0;i <= right;i ++){
sum += A[i];
}
if((sum % 3) != 0){
return false;
}else{
int sum_left = A[0];
int sum_right = A[right];
//因为是分成三部分,因此条件是:left + 1 < right
while(left + 1 < right){
//判断如果左右指针都符合要求,那么就返回正确
if((sum_right == sum / 3) && (sum_left == sum / 3)){
return true;
}
//如果左指针不符合要求就向右移动左指针,否则不动
if(sum_left != sum / 3){
sum_left += A[++ left];
}
//如果右指针不符合要求就向左移动右指针,否则不动
if(sum_right != sum / 3){
sum_right += A[-- right];
}
}
return false;
}
}
}
}
解法:采用整体排序的思想,先把数组深拷贝,然后从两端进行比较,数据不一样的即为边界
class Solution {
public int[] subSort(int[] array) throws NullPointerException{
int[] ans = new int[]{-1,-1};
if(array.length < 0){
throw new NullPointerException();
}else if(array.length == 1){
return ans;
}else{
//这个题的关键思路就在先深拷贝数组,然后对它进行排序
int[] copy = Arrays.copyOf(array, array.length);
Arrays.sort(copy);
int left = 0;
int right = array.length - 1;
boolean flag1 = false;
boolean flag2 = false;
while(left < right){
if(array[left] == copy[left]){
left ++;
}else{
flag1 = true;
}
if(array[right] == copy[right]){
right -- ;
}else{
flag2 = true;
}
if(flag1 && flag2){
break;
}
}
if(left < right){
ans[0] = left;
ans[1] = right;
}
}
return ans;
}
}
解法:考虑二分查找的思想,right = 重量和,left =重量的最大值。由于要求的是D天内送达的最小装载能力。因此采用二分左限界查找。也就是说,即使mid满足条件,也不返回,让right = mid,当mid不满足条件,让left = mid + 1。最后返回left。
class Solution {
//二分查找左限界法
public int shipWithinDays(int[] weights, int D) {
if(weights.length <= 0){
return -1;
}
int max = 0;
int min = weights[0];
for(int i : weights){
max += i;
min = Math.max(min, i);
}
while(min < max){
int mid = min + (max - min) / 2;
//区间变为[left,mid]
if(judge(weights,D,mid)){
max = mid;
}else{
min = mid + 1;
}
}
return min;
}
private boolean judge(int[] weights,int D,int weight){
int consist = weight;
for(int i = 0;i < weights.length;i ++){
if(consist < weights[i]){
return false;
}
if(weight - weights[i] < 0){
weight = consist - weights[i];
D --;
}else{
weight -= weights[i];
}
}
return D > 0;
}
}
解法:和上题类似,采用的是左限界的二分查找,left = piles数组最小的数,right = piles数组最大的数。
class Solution {
public int minEatingSpeed(int[] piles, int H) {
if(piles.length < 0){
throw new NullPointerException();
}
int max = 0;
int min = 0;
for(int i = 0;i < piles.length;i ++){
if(min > piles[i]){
min = piles[i];
}
if(max < piles[i]){
max = piles[i];
}
}
//待求解的是最小速度,因此采用左限界二分查找
while(min < max){
int mid = min + (max - min) / 2;
if(judgeMinEatingSpeed(piles, H, mid)){
max = mid;
}else{
min = mid + 1;
}
}
return min;
}
public boolean judgeMinEatingSpeed(int[] piles,int H,int ans){
if(ans == 0){
return false;
}
int time = 0;
for(int i = 0;i < piles.length;i ++){
if(piles[i] == 0){
time ++;
}else{
if(piles[i] % ans == 0){
time += piles[i] / ans;
}else{
time += (piles[i] / ans) + 1;
}
}
}
if(H >= time){
return true;
}else{
return false;
}
}
}
class Solution {
public int titleToNumber(String s) {
if(s == null && s.length() == 0){
return 0;
}
int ans = 0;
for(int i = 0;i < s.length();i ++){
if(s.charAt(i) >= 'A' && s.charAt(i) <= 'Z'){
int value = s.charAt(i) - 'A' + 1;
//第i个数乘i - 1次
ans = ans * 26 + value;
}else{
throw new IllegalArgumentException();
}
}
return ans;
}
}
解法:26进制转10进制,需要注意的是1对应A,但是0没有对应,因此在输入的数大于26的时候要进行n --
class Solution {
public String convertToTitle(int n) {
if(n < 0){
throw new IllegalArgumentException();
}else if(n == 0){
return "";
}else{
StringBuilder sb = new StringBuilder();
Stack stack = new Stack<>();
while(n > 26){
//原本这个n --没有,但是由于0没有字母对应,因此先进进行n --
n --;
int m = n % 26;
//stack.push((char)('A' + m - 1));
stack.push((char)('A' + m));
n = n / 26;
}
if(n > 0){
stack.push((char)('A' + n - 1));
}
while( ! stack.isEmpty()){
sb.append(stack.pop());
}
return sb.toString();
}
}
}
解法:模拟进位,从低位向高位相加即可。
class Solution {
public String addStrings(String num1, String num2) {
if((num1 == null && num1 == "") || (num2 == null && num2 == "")){
return null;
}
StringBuilder sb = new StringBuilder();
StringBuilder ans = new StringBuilder();
int index1 = num1.length() - 1;
int index2 = num2.length() - 1;
int add = 0;
while(index1 >= 0 && index2 >= 0){
//把char转化为int
int number1 = Character.getNumericValue(num1.charAt(index1));
int number2 = Character.getNumericValue(num2.charAt(index2));
int num = number1 + number2 + add;
add = num / 10;
//StringBulider可以直接追加int
sb.append(num % 10);
index1 --;
index2 --;
}
while(index1 >= 0){
int number1 = Character.getNumericValue(num1.charAt(index1));
int num = number1 + add;
add = num / 10;
//StringBulider可以直接追加int
sb.append(num % 10);
index1 --;
}
while(index2 >= 0){
int number2 = Character.getNumericValue(num2.charAt(index2));
int num = number2 + add;
add = num / 10;
//StringBulider可以直接追加int
sb.append(num % 10);
index2 --;
}
if(add > 0){
sb.append(add);
}
for(int i = sb.length() - 1;i >= 0;i --){
ans.append(sb.charAt(i));
}
return ans.toString();
}
}