回文数(leetcode9)
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。(不能将整数转为字符串)
解题思路: 取出后半段数字进行翻转。
由于回文数的位数可奇可偶,所以当它的长度是偶数时,它对折过来应该是相等的;当它的长度是奇数时,那么它对折过来后,有一个的长度需要去掉一位数(除以 10 并取整)。
具体做法如下:
每次进行取余操作 ( %10),取出最低的数字:y = x % 10;
将最低的数字加到取出数的末尾:revertNum = revertNum * 10 + y;
每取一个最低位数字,x 都要自除以 10;
判断 x 是不是小于 等于revertNum ,当它小于等于的时候,说明数字已经对半或者过半了;
最后,判断奇偶数情况:如果是偶数的话,revertNum 和 x 相等;如果是奇数的话,最中间的数字就在revertNum 的最低位上,将它除以 10 以后应该和 x 相等。
复杂度分析
时间复杂度:O(logn),对于每次迭代,我们会将输入除以 10,因此时间复杂度为O(logn)。
空间复杂度:O(1)。我们只需要常数空间存放若干变量。
class Solution {
public boolean isPalindrome(int x) {
// 特殊情况:当 x < 0 时,x 不是回文数。
// 如果数字的最后一位是0,为了回文,则其第一位数字也应该是0,只有0满足这一属性.
if(x<0||(x%10==0&&x!=0)) return false;
int tmp =0;
while(x>tmp){
tmp = tmp*10+x%10;
x/=10;
}
return x==tmp||x==tmp/10;
}
}
最长回文子串(leetcode5)
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 :输入: “babad” 输出: “bab” 注意: “aba” 也是一个有效答案。
解题思路:
动态规划:一个回文去掉两头以后,剩下的部分依然是回文。
如果一个字符串的头尾两个字符都不相等,那么这个字符串一定不是回文串;
如果一个字符串的头尾两个字符相等,如果里面的子串是回文,整体就是回文串;如果里面的子串不是回文串,整体就不是回文串。
1、dp[i][j] 表示子串 s[i…j] 是否为回文子串(左闭右闭区间)。
2、dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
边界条件是:表达式 [i + 1, j - 1] 不构成区间,即j - i < 3。只需要判断一下头尾两个字符是否相等就可以直接下结论了。因此,在 s[i] == s[j] 成立和 j - i < 3 的前提下,直接可以下结论,dp[i][j] = true,否则才执行状态转移。
3、初始化的时候,单个字符一定是回文串,因此把对角线先初始化为 true,即 dp[i][i] = true 。
4、只要一得到 dp[i][j] = true,就记录子串的长度和起始位置,没有必要截取,这是因为截取字符串也要消耗性能,记录此时的回文子串的「起始位置」和「回文长度」即可。
class Solution {
public String longestPalindrome(String s) {
if(s==null||s.length()<2) return s;
int max = 1;
int start =0;
boolean [][] dp = new boolean[s.length()][s.length()];
char[] cs = s.toCharArray();
for(int i=0;i<s.length();i++){
dp[i][i] = true;
}
for(int j=1;j<s.length();j++){
for(int i=0;i<j;i++){
if(cs[i]!=cs[j]) dp[i][j]=false;
else{
if(j-i<3) dp[i][j]= true;
else dp[i][j] = dp[i+1][j-1];
}
if(dp[i][j]&&(j-i+1)>max){
max = j-i+1;
start = i;
}
}
}
return s.substring(start,start+max);
}
}
最长不含重复字符的子字符串
剑指 Offer 48. 最长不含重复字符的子字符串(leetcode)
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
示例 1:输入: “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
class Solution {
public int lengthOfLongestSubstring(String s) {
int max = 0 ;
int start = -1;//不包括start,start从-1开始
HashMap<Character,Integer> map = new HashMap<>();
for(int i=0;i<s.length();i++){
char tmp = s.charAt(i);
if(map.containsKey(tmp)) start = Math.max(start,map.get(tmp));
map.put(tmp,i);
max = Math.max(max,i-start);//不包括start
}
return max;
}
}
牛客版本:给定一个数组arr,返回arr的最长无重复子串的长度(无重复指的是所有数字都不相同)。
示例1:输入 :[2,3,4,5] 输出:4
import java.util.*;
public class Solution {
public int maxLength (int[] arr) {
// write code here
int max =0;
int start =-1;
HashMap<Integer,Integer> map = new HashMap<>();
for(int i=0;i<arr.length;i++){
if(map.containsKey(arr[i])) start = Math.max(start,map.get(arr[i]));
map.put(arr[i],i);
max = Math.max(max,i-start);
}
return max;
}
}
最长公共子串
1.最长公共子数组(LeetCode718)输出长度
给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。示例:输入:A: [1,2,3,2,1] B: [3,2,1,4,7] 输出:3
解释: 长度最长的公共子数组是 [3, 2, 1] 。
递推公式
if(s1.charAt(i) == s2.charAr(j))
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = 0;
class Solution {
public int findLength(int[] A, int[] B) {
if(A.length==0||B.length==0) return 0;
int max=0;
int[][] dp = new int[A.length+1][B.length+1];
for(int i=1;i<=A.length;i++){
for(int j=1;j<=B.length;j++){
if(A[i-1]==B[j-1]) dp[i][j] = dp[i-1][j-1]+1;
else dp[i][j] = 0;
max = Math.max(max,dp[i][j]);
}
}
return max;
}
}
2. 最长公共子串(牛客版,输出结果)
题目描述: 给定两个字符串str1和str2,输出两个字符串的最长公共子串,如果最长公共子串为空,输出-1。
示例1:输入:“1AB2345CD”,“12345EF”,输出:“2345”
import java.util.*;
public class Solution {
public String LCS (String str1, String str2) {
// write code here
if(str1==null||str2==null||str1.length()==0||str2.length()==0) return "-1";
int max = 0;
int indexEnd1=0;
int[][] dp = new int[str1.length()+1][str2.length()+1];
for(int i=1;i<=str1.length();i++){
for(int j=1;j<=str2.length();j++){
if(str1.charAt(i-1)==str2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1]+1;
if(dp[i][j]>max){
max = dp[i][j];
indexEnd1 = i-1;
}
}
else dp[i][j] = 0;
}
}
if(max==0) return "-1";
return str1.substring(indexEnd1-max+1,indexEnd1+1);
}
}
最长公共子序列
最长公共子序列(LeetCode1143)输出长度
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。
示例 1:
输入:text1 = “abcde”, text2 = “ace”
输出:3
解释:最长公共子序列是 “ace”,它的长度为 3。
递推公式:
if(s1.charAt(i)==s2.charAr(j))
dp[i][j]=dp[i-1][j-1]+1;
else
dp[i][j]=Math.max(dp[i -1][j],dp[i][j -1]);
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
if(text1==null||text2==null||text1.length()==0||text2.length()==0) return 0;
int[][] dp = new int[text1.length()+1][text2.length()+1];
for(int i=1;i<=text1.length();i++){
for(int j=1;j<=text2.length();j++){
if(text1.charAt(i-1)==text2.charAt(j-1)) dp[i][j] = dp[i-1][j-1]+1;
else dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[text1.length()][text2.length()];
}
}
最长上升子序列
- 最长上升子序列(LeetCode 300):给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length==0) return 0;
ArrayList<Integer> list = new ArrayList<>();
list.add(nums[0]);
for(int i=1;i<nums.length;i++){
if(nums[i]>list.get(list.size()-1)){
list.add(nums[i]);
}else{
int start =0;
int end = list.size()-1;
int index =0;
while(start<=end){
int mid = start+(end-start)/2;
if(list.get(mid)>=nums[i]){
if(mid==0||list.get(mid-1)<nums[i]){
index = mid;
break;
}
end = mid-1;
}else{
start = mid+1;
}
}
list.set(index,nums[i]);
}
}
return list.size();
}
}
2.(进阶版)最长递增子序列(牛客版)输出具体子序列
给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)
示例1 输入[2,1,5,3,6,4,8,9,7],输出[1,3,4,8,9]。
import java.util.*;
public class Solution {
public int[] LIS (int[] arr) {
// write code here
if(arr.length==0) return new int[0];
ArrayList<Integer> list = new ArrayList<>();
ArrayList<Integer> max = new ArrayList<>();
list.add(arr[0]);
max.add(1);
for(int i=1;i<arr.length;i++){
if(arr[i]>=list.get(list.size()-1)){
list.add(arr[i]);
max.add(list.size());
}else{
int start = 0;
int end = list.size()-1;
int index =0;
while(start<=end){
int mid = start+(end-start)/2;
if(list.get(mid)>=arr[i]){
if(mid==0||list.get(mid-1)<arr[i]){
index = mid;
break;
}
end = mid-1;
}else{
start = mid+1;
}
}
list.set(index,arr[i]);
max.add(index+1);
}
}
int[] res = new int[list.size()];
int length = list.size();
for(int i=arr.length-1;i>=0;i--){
if(length<=0) break;
if(max.get(i)==length){
res[--length] = arr[i];
}
}
return res;
}
}
最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 “”。
示例 1: 输入: [“flower”,“flow”,“flight”] 输出: “fl”
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs.length==0) return "";
String res = strs[0];
for(int i=1;i<strs.length;i++){
int j=0;
for(;j<res.length()&&j<strs[i].length();j++){
if(res.charAt(j)!=strs[i].charAt(j)) break;
}
res = res.substring(0,j);
if(res.length()==0) return "";
}
return res;
}
}
连续子数组的最大和
剑指 Offer 42. 连续子数组的最大和
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。
示例1: 输入: nums = [-2,1,-3,4,-1,2,1,-5,4] 输出: 6
class Solution {
public int maxSubArray(int[] nums) {
//if(nums.length==0) return 0;//此处题目中规定nums不为空,所以这句可以不加
int max = nums[0];
int former = 0;//用于记录dp[i-1]的值,对于dp[0]而言,其前面的dp[-1]=0
int cur = nums[0];//用于记录dp[i]的值
for(int num:nums){
cur = num;
if(former>0) cur +=former;
if(cur>max) max = cur;
former=cur;
}
return max;
}
}
买卖股票的最好时机
剑指 Offer 63. 股票的最大利润
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
示例 1: 输入: [7,1,5,3,6,4] 输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
class Solution {
public int maxProfit(int[] prices) {
if(prices.length==0) return 0;//当min取prices[0]时,这句就不能省略了;
int min = prices[0];
int profit = 0; //初始设置为0,可以保证后面得到的profit>=0
for(int p:prices){
if(p<min) min = p;//如果当前的价格小于<min,则对min进行更新,即重新进行买入,因为之后遍历的数字减去当前的数字可获得更大的利润
else//当上面的if成立时,默认进行买入操作,显然不需要对profit进行更新
profit = profit>p-min?profit:p-min;//对min进行更新后,相当于在更新的min处进行买入,则后面的每一个>=min的值均可以进行卖出,即依次取之后遍历的p值卖出,并计算profit,若比当前的profit大时,进行更新
}
return profit;
}
}
设计LRU缓存结构
设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能:
set(key, value):将记录(key, value)插入该结构
get(key):返回key对应的value值
[要求]set和get方法的时间复杂度为O(1);
某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案。
示例: 输入 [[1,1,1],[1,2,2],[1,3,2],[2,1],[1,4,4],[2,2]],3
输出 [1,-1]
import java.util.*;
public class Solution {
/**
* lru design
* @param operators int整型二维数组 the ops
* @param k int整型 the k
* @return int整型一维数组
*/
class Node{
int key;
int val;
Node pre;
Node next;
public Node(int key,int val){
this.val = val;
this.key = key;
}
}
Node head = new Node(0,0);
Node tail = new Node(0,0);
int size = 0;
HashMap<Integer,Node> map = new HashMap<>();
public int[] LRU (int[][] operators, int k) {
// write code here
head.next = tail;
tail.pre = head;
ArrayList<Integer> list = new ArrayList<>();
for(int i=0;i<operators.length;i++){
if(operators[i][0]==1){
set(operators[i][1],operators[i][2],k);
}else{
list.add(get(operators[i][1]));
}
}
int[] res = new int[list.size()];
for(int i=0;i<res.length;i++){
res[i] = list.get(i);
}
return res;
}
public void set(int key,int value,int k){
Node node = map.get(key);
if(node==null){
Node newNode = new Node(key,value);
addToHead(newNode);
map.put(key,newNode);
size++;
if(size>k){
map.remove(moveTail().key);
size--;
}
}else{
node.val = value;
moveToHead(node);
}
}
public int get(int key){
Node node = map.get(key);
if(node==null) return -1;
else{
moveToHead(node);
return node.val;
}
}
public void addToHead(Node node){
Node tmp = head.next;
head.next = node;
node.pre = head;
node.next = tmp;
tmp.pre = node;
}
public Node moveTail(){
Node res = tail.pre;
moveNode(res);
return res;
}
public void moveNode(Node node){
Node tmp1 = node.pre;
Node tmp2 = node.next;
tmp1.next = tmp2;
tmp2.pre = tmp1;
}
public void moveToHead(Node node){
moveNode(node);
addToHead(node);
}
}
二分查找
请实现有重复数字的有序数组的二分查找。输出在数组中第一个大于等于查找值的位置(从1开始),如果数组中不存在这样的数,则输出数组长度加一。
示例:输入:5,4,[1,2,4,4,5],输出:3。
import java.util.*;
public class Solution {
/**
* 二分查找
* @param n int整型 数组长度
* @param v int整型 查找值
* @param a int整型一维数组 有序数组
* @return int整型
*/
public int upper_bound_ (int n, int v, int[] a) {
// write code here
int start = 0;
int end = n-1;
while(start<=end){
int mid = start+(end-start)/2;
if(a[mid]>=v){
if(mid==0||a[mid-1]<v) return mid+1;
else end = mid-1;
}else{
start = mid+1;
}
}
return n+1;
}
}
平方根
x 的平方根(LeetCode69)
实现 int sqrt(int x) 函数。计算并返回 x 的平方根,其中 x 是非负整数。由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
class Solution {
public int mySqrt(int x) {
int start = 0, end = x, ans = -1;
while (start <= end) {
int mid = start + (end - start) / 2;
if ((long) mid * mid <= x) {
ans = mid;
start = mid + 1;
} else {
end = mid - 1;
}
}
return ans;
}
}
最小的K个数
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
if(k==0||k>input.length) return new ArrayList<Integer>();
QuickSort(input,0,input.length-1,k);
ArrayList<Integer> res = new ArrayList<>();
for(int i=0;i<k;i++){
res.add(input[i]);
}
return res;
}
public void QuickSort(int[] input,int start,int end,int k){
int i = start;
int j = end;
int key = input[start];
while(i<j){
while(i<j&&input[j]>=key){
j--;
}
while(i<j&&input[i]<=key){
i++;
}
if(i<j){
int tmp = input[i];
input[i] = input[j];
input[j] = tmp;
}
}
input[start] = input[i];
input[i] = key;
if(i==k-1) return;
else if(i>k-1) QuickSort(input,start,i-1,k);
else QuickSort(input,i+1,end,k);
}
}
寻找第K大
有一个整数数组,请你根据快速排序的思路,找出数组中第K大的数。
给定一个整数数组a,同时给定它的大小n和要找的K(K在1到n之间),请返回第K大的数,保证答案存在。
样例:[1,3,5,2,2],5,3 返回:2。
import java.util.*;
public class Finder {
int res = Integer.MIN_VALUE;
public int findKth(int[] a, int n, int K) {
// write code here
QuickSort(a,0,n-1,K);
return res;
}
public void QuickSort(int[] a,int start,int end,int K){
if(start>end) return;
int i = start;
int j = end;
int key = a[start];
while(i<j){
while(i<j&&a[j]<=key){
j--;
}
while(i<j&&a[i]>=key){
i++;
}
if(i<j){
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
a[start] = a[i];
a[i] =key;
if(i==K-1){
res = a[i];
return;
}else if(i>K-1){
QuickSort(a,start,i-1,K);
}else{
QuickSort(a,i+1,end,K);
}
}
}
合并两个有序的数组
合并两个有序数组(LeetCode88)
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
说明:初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
示例:输入:nums1 = [1,2,3,0,0,0], m = 3,nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
空间复杂度为o(1),最坏时间复杂度是o(m+n),最好时间复杂度是o(n)。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = m - 1, j = n - 1, index = m + n - 1;
while (i >= 0 && j >= 0)
nums1[index--] = nums1[i] > nums2[j] ? nums1[i--] : nums2[j--];
while (j >= 0)
nums1[index--] = nums2[j--];
}
}
两数之和
- 两数之和(leetcode 1)
给定一个整数数组 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) {
HashMap<Integer,Integer>map = new HashMap<>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(target-nums[i])) return new int[]{map.get(target-nums[i]),i};
map.put(nums[i],i);
}
return new int[0];
}
}
牛客版:给出一个整数数组,请在数组中找出两个加起来等于目标值的数,你给出的函数twoSum 需要返回这两个数字的下标(index1,index2),需要满足 index1 小于index2。
注意:下标是从1开始的, 假设给出的数组中只存在唯一解
import java.util.*;
public class Solution {
public int[] twoSum (int[] numbers, int target) {
// write code here
HashMap<Integer,Integer> map = new HashMap<>();
for(int i=0;i<numbers.length;i++){
if(map.containsKey(target-numbers[i]))
return new int[]{map.get(target-numbers[i])+1,i+1};
map.put(numbers[i],i);
}
return new int[0];
}
}
三数之和
给出一个有n个元素的数组S,S中是否有元素a,b,c满足a+b+c=0?找出数组S中所有满足条件的三元组。
注意:三元组(a、b、c)中的元素必须按非降序排列。(即a≤b≤c)
解集中不能包含重复的三元组。
例如,给定的数组 S = {-10 0 10 20 -10 -40},解集为(-10, 0, 10) (-10, -10, 20)
import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer>> threeSum(int[] num) {
ArrayList<ArrayList<Integer>> list = new ArrayList<>();
if(num.length<3) return list;
int len = num.length;
Arrays.sort(num);
for(int i=0;i<len-2;i++){
if(num[i]>0) break;
if(i>0&&num[i]==num[i-1]) continue;
int start = i+1;
int end = len-1;
while(start<end){
int sum = num[i]+num[start]+num[end];
if(sum==0){
ArrayList<Integer> tmp = new ArrayList<>();
tmp.add(num[i]);
tmp.add(num[start]);
tmp.add(num[end]);
list.add(tmp);
while(start<end&&num[start]==num[start+1]){
start++;
}
while(start<end&&num[end]==num[end-1]){
end--;
}
start++;
end--;
}else if(sum<0){
start++;
}else{
end--;
}
}
}
return list;
}
}
两数相加(链表/字符串)
两数相加(leetcode2):
给出两个非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储一位数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = new ListNode(0);
ListNode cur = head;
int up = 0;
int sum = 0;
while(l1!=null||l2!=null){
int factor1 = l1==null?0:l1.val;
int factor2 = l2==null?0:l2.val;
sum = factor1+factor2+up;
up = sum/10;
cur.next = new ListNode(sum%10);
cur = cur.next;
if(l1!=null) l1=l1.next;
if(l2!=null) l2=l2.next;
}
if(up!=0) cur.next = new ListNode(up);
return head.next;
}
}
两个链表生成相加链表(牛客网题目):
链表为正序时:
假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。给定两个这种链表,请生成代表两个整数相加值的结果链表。
例如:链表 1 为 9->3->7,链表 2 为 6->3,最后生成新的结果链表为 1->0->0->0。
import java.util.*;
public class Solution {
public ListNode addInList (ListNode head1, ListNode head2) {
// write code here
Stack<ListNode> stack1 = new Stack<>();
Stack<ListNode> stack2 = new Stack<>();
while(head1!=null){
stack1.push(head1);
head1= head1.next;
}
while(head2!=null){
stack2.push(head2);
head2 = head2.next;
}
ListNode head = new ListNode(0);
ListNode cur = head;
int up=0;
int factor1 =0;
int factor2 =0;
int sum =0;
while(!stack1.isEmpty()||!stack2.isEmpty()){
ListNode node1=null; //这里位置不能改变
ListNode node2=null;
if(!stack1.isEmpty()) node1=stack1.pop();
if(!stack2.isEmpty()) node2= stack2.pop();
factor1 = node1==null?0:node1.val;
factor2 = node2==null?0:node2.val;
sum = factor1+factor2+up;
up=sum/10;
cur.next = new ListNode(sum%10);
cur = cur.next;
}
if(up!=0) cur.next = new ListNode(up);
cur = head.next;
ListNode pre = null;
while(cur!=null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
字符串相加
字符串相加LeetCode415:给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。
提示:num1 和num2 的长度都小于 5100,num1 和num2 都只包含数字 0-9
num1 和num2 都不包含任何前导零,你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式。
class Solution {
public String addStrings(String num1, String num2) {
StringBuilder res = new StringBuilder("");
int i = num1.length() - 1, j = num2.length() - 1, carry = 0;
while(i >= 0 || j >= 0){
int n1 = i >= 0 ? num1.charAt(i) - '0' : 0;
int n2 = j >= 0 ? num2.charAt(j) - '0' : 0;
int tmp = n1 + n2 + carry;
carry = tmp / 10;
res.append(tmp % 10);
i--; j--;
}
if(carry == 1) res.append(1);
return res.reverse().toString();
}
}
数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
if(array.length==0) return 0;
int res = array[0];
int vote =0;
for(int num:array){
if(vote==0) res=num;
vote+=res==num?1:-1;
}
int count = 0;
for(int num:array){
if(num==res) count++;
}
return count>array.length/2?res:0;
}
}
二维数组中的查找
剑指 Offer 04.二维数组中的查找
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]给定 target = 5,返回 true。给定 target = 20,返回 false。
思路: 由于给定的二维数组具备每行从左到右递增以及每列从上到下递增的特点,当访问到一个元素时,可以排除数组中的部分元素。
从二维数组的右上角开始查找。如果当前元素等于目标值,则返回 true。如果当前元素大于目标值,则移到左边一列。如果当前元素小于目标值,则移到下边一行。
(由于数组从左向右递增,从上到下递增,因此从右上角或左下角出发可行,从左上角或者右下角出发不可行。)
时间复杂度:O(n+m)。访问到的下标的行最多增加 n 次,列最多减少 m 次,因此循环体最多执行 n + m 次。
空间复杂度:O(1)。
class Solution {
public boolean findNumberIn2DArray(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;
if(matrix[i][j]>target) i--;
else j++;
}
return false;
}
}
接雨水+盛水最多的容器
注意:这两道题都使用双指针解法,但是题意完全不同。
1.接雨水(LeetCode42)
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
复杂性分析:
时间复杂度:O(n),空间复杂度:O(1)。
class Solution {
public int trap(int[] height) {
int left = 0, right = height.length - 1;
int ans = 0;
int left_max = 0, right_max = 0;
while (left < right) {
if (height[left] < height[right]) {
if (height[left] >= left_max) {
left_max = height[left];
} else {
ans += (left_max - height[left]);
}
++left;
} else {
if (height[right] >= right_max) {
right_max = height[right];
} else {
ans += (right_max - height[right]);
}
--right;
}
}
return ans;
}
}
2.盛最多水的容器(LeetCode11)
给你 n 个非负整数 a1,a2,…,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
思路: 设置双指针 ii,jj 分别位于容器壁两端,根据规则移动指针,并且更新面积最大值 res,直到 i == j 时返回 res。
复杂度分析:时间复杂度 O(N),空间复杂度 O(1)。
class Solution {
public int maxArea(int[] height) {
int i = 0, j = height.length - 1, res = 0;
while(i < j){
res = height[i] < height[j] ?
Math.max(res, (j - i) * height[i++]):
Math.max(res, (j - i) * height[j--]);
}
return res;
}
}
括号序列
给出一个仅包含字符’(’,’)’,’{’,’}’,’[‘和’]’,的字符串,判断给出的字符串是否是合法的括号序列。括号必须以正确的顺序关闭,"()“和”()[]{}“都是合法的括号序列,但”(]“和”([)]“不合法。
示例:输入”[" 输出 false
import java.util.*;
public class Solution {
public boolean isValid (String s) {
// write code here
HashMap<Character,Character>map = new HashMap<>();
map.put('(',')');
map.put('{','}');
map.put('[',']');
map.put('$','$');
Stack<Character> stack = new Stack<>();
stack.push('$');
for(char c:s.toCharArray()){
if(map.containsKey(c)) stack.push(c);
else if(c!=map.get(stack.pop())) return false;
}
return stack.size()==1;
}
}
数组中数字出现的次数
1.剑指 Offer 56 - I. 数组中数字出现的次数
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:输入:nums = [4,1,4,6] 输出:[1,6] 或 [6,1]
class Solution {
public int[] singleNumbers(int[] nums) {
int tmp=0;
int digit =1;
int[] res = new int[2];
for(int num:nums) tmp^=num;
while((digit&tmp)==0) digit=digit<<1;
for(int num:nums){
if((digit&num)==0) res[0]^=num;
else res[1]^=num;
}
return res;
}
}
2.剑指 Offer 56 - II. 数组中数字出现的次数 II
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:输入:nums = [3,4,3,3] 输出:4
class Solution {
public int singleNumber(int[] nums) {
int[] count = new int[32];
for(int num:nums){
int digit =1;
for(int i=31;i>=0;i--){
if((num&digit)!=0) count[i]++;
digit = digit<<1;
}
}
int res =0;
for(int i=0;i<32;i++){
res = res<<1;
res+=count[i]%3;
}
return res;
}
}
3.牛客版本:数组中只出现一次的数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int tmp =0;
int digit =1;
for(int i:array) tmp^=i;
while((tmp&digit)==0) digit = digit<<1;
for(int i:array){
if((i&digit)==0) num1[0]^=i;
else num2[0]^=i;
}
}
}
斐波那契数列
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。n<=39
public class Solution {
public int Fibonacci(int n) {
if(n==0) return 0;
if(n==1) return 1;
int f0=0;
int f1=1;
int sum=0;
for(int i=0;i<=n-2;i++){
sum = f0+f1;
f0=f1;
f1=sum;
}
return sum;
}
}
跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
public class Solution {
public int JumpFloor(int target) {
if(target==0) return 1;
if(target==1) return 1;
int f0 = 1;
int f1 = 1;
int sum =0;
for(int i=0;i<=target-2;i++){
sum = f0+f1;
f0 = f1;
f1 = sum;
}
return sum;
}
}
剪绳子
剑指 Offer 14- I.
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
示例 1:输入: 2输出: 1解释: 2 = 1 + 1, 1 × 1 = 1。
思路:贪心算法。
class Solution {
public int cuttingRope(int n) {
if(n==2)
return 1;
if(n==3)
return 2;
if(n==4)
return 4;
int temp=1;
while(n>=5) {
n=n-3;
temp=3*temp;
}
//temp乘上最后一小段小于5的绳子
return n*temp;
}
}
剑指 Offer 14- II. 剪绳子 II
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m - 1] 。请问 k[0]k[1]…*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
class Solution {
public int cuttingRope(int n) {
if(n==2)
return 1;
if(n==3)
return 2;
if(n==4)
return 4;
long temp=1;//不能使用int,int的存储范围不够
while(n>=5) {
n=n-3;
temp=3*temp%1000000007;
}
//temp乘上最后一小段小于5的绳子
return (int)(n*temp%1000000007);
//这里转换时 (temp*n%1000000007)外面的括号不能省略
}
}
用两个栈实现队列
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
public int pop() {
if(stack2.isEmpty()){
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
}
if(stack2.isEmpty()){
return -1;
}else{
return stack2.pop();
}
}
}
数字/字符串全排列
全排列(leetcode 46)(无重复数字时)
给定一个 没有重复数字的序列,返回其所有可能的全排列。
示例: 输入: [1,2,3] 输出:
[ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if(nums.length==0) return res;
List<Integer> cur = new ArrayList<>();
int[] visited = new int[nums.length];
dfs(res,cur,nums,visited);
return res;
}
public void dfs(List<List<Integer>> res,List<Integer> cur,int[] nums,int[]visited){
if(cur.size()==nums.length){
res.add(new ArrayList<Integer>(cur));
return;
}
for(int i=0;i<nums.length;i++){
if(visited[i]==1) continue;
visited[i]=1;
cur.add(nums[i]);
dfs(res,cur,nums,visited);
visited[i]=0;
cur.remove(cur.size()-1);
}
}
}
全排列 II(leetcode47)
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例: 输入: [1,1,2] 输出:[ [1,1,2], [1,2,1], [2,1,1]]。
class Solution {
boolean[] vis;
public List<List<Integer>> permuteUnique(int[] nums) {
List<List<Integer>> ans = new ArrayList<List<Integer>>();
List<Integer> perm = new ArrayList<Integer>();
vis = new boolean[nums.length];
Arrays.sort(nums);
backtrack(nums, ans, 0, perm);
return ans;
}
public void backtrack(int[] nums, List<List<Integer>> ans, int idx, List<Integer> perm) {
if (idx == nums.length) {
ans.add(new ArrayList<Integer>(perm));
return;
}
for (int i = 0; i < nums.length; ++i) {
if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
continue;
}
perm.add(nums[i]);
vis[i] = true;
backtrack(nums, ans, idx + 1, perm);
vis[i] = false;
perm.remove(idx);
}
}
}
字符串的排列(牛客版本,要求输出字典序)
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则按字典序打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
思路:字典序法
参考https://blog.csdn.net/babynumber/article/details/42706757
在O(n)的时间复杂度下生成当前排列的下一个排列(字典序)。详细算法去读上面的博客。
简单的讲:
1、从右向左找到第一个正序对(array[i] < array[i+1],因为没有等号,所以可以完美去掉重复的排列)
2、从i开始向右搜索,找到比array[i]大的字符中最小的那个,记为array[j]
3、交换array[i]和array[j]
4、将i后面的字符反转
这就得到了字典序的下一个排列。连续使用这个方法则可从字典序最小的排列推出全部排列。时间复杂度O(n*n!)
import java.util.*;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> res = new ArrayList<String>();
if(str.length() == 0) return res;
char [] array = str.toCharArray();
Arrays.sort(array);
String s = new String(array);
res.add(str);
while(true){
s = nextString(s);
if(!s.equals("finish")){
res.add(s);
}
else{
break;
}
}
return res;
}
public String nextString(String str){
char [] array = str.toCharArray();
int length = str.length();
int i = length-2;
for(; i>=0 && array[i] >= array[i+1]; i--);
if(i == -1) return "finish";
int j = length-1;
for(; j>=0 && array[j] <= array[i]; j--);
//swap i,j
char tmp = array[i];
array[i] = array[j];
array[j] = tmp;
//swap i,j
for(int a=i+1, b=length-1; a<b;a++,b--){
tmp = array[a];
array[a] = array[b];
array[b] = tmp;
}
return new String(array);
}
}
计算器
给定一个包含正整数、加(+)、减(-)、乘()、除(/)的算数表达式(括号除外),计算其结果。表达式仅包含非负整数,+, - ,,/ 四种运算符和空格 。 整数除法仅保留整数部分。
示例 1:输入: "3+2*2"输出: 7
class Solution {
public int calculate(String s) {
if(s==null||s.length()==0) return 0;
s=s.replaceAll(" ","");
int n=0;
char preflag='+';
Stack<Integer> stack = new Stack<>();
for(int i=0;i<s.length();i++){
char cur = s.charAt(i);
if(Character.isDigit(cur)){
n=n*10+cur-'0';
}
if(!Character.isDigit(cur)||i==s.length()-1){
if(preflag=='+') stack.push(n);
else if(preflag=='-') stack.push(-n);
else if(preflag=='*') {
int tmp = stack.pop();
stack.push(tmp*n);
}else{
int tmp = stack.pop();
stack.push(tmp/n);
}
preflag = cur;
n=0;
}
}
int res =0;
while(!stack.isEmpty()){
res+=stack.pop();
}
return res;
}
}
岛屿数量
岛屿数量(LeetCode200)
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
示例 1:输入:grid = [
[“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”]]输出:1
class Solution {
public int numIslands(char[][] grid) {
int count=0;
for(int i=0;i<grid.length;i++){
for(int j=0;j<grid[0].length;j++){
if(grid[i][j]=='1'){
dfs(grid,i,j);
count++;
}
}
}
return count;
}
public void dfs(char[][] grid,int i,int j){
if(i<0||j<0||i>=grid.length||j>=grid[0].length||grid[i][j]=='0') return;
grid[i][j]='0';
dfs(grid,i-1,j);
dfs(grid,i+1,j);
dfs(grid,i,j-1);
dfs(grid,i,j+1);
}
}
岛屿的最大面积
岛屿的最大面积(LeetCode695)
给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[i].length; j++) {
if (grid[i][j] == 1) {
res = Math.max(res, dfs(i, j, grid));
}
}
}
return res;
}
// 每次调用的时候默认num为1,进入后判断如果不是岛屿,则直接返回0,就可以避免预防错误的情况。
// 每次找到岛屿,则直接把找到的岛屿改成0,这是传说中的沉岛思想,就是遇到岛屿就把他和周围的全部沉默。
private int dfs(int i, int j, int[][] grid) {
if (i < 0 || j < 0 || i >= grid.length
|| j >= grid[i].length || grid[i][j] == 0) {
return 0;
}
grid[i][j] = 0;
int num = 1;
num += dfs(i + 1, j, grid);
num += dfs(i - 1, j, grid);
num += dfs(i, j + 1, grid);
num += dfs(i, j - 1, grid);
return num;
}
}
被围绕的区域
被围绕的区域 LeetCode130
给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例: 运行你的函数后,矩阵变为:
X X X X X X X X
X O O X X X X X
X X O X X X X X
X O X X X O X X
解释: 被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
思路: 对于每一个边界上的 O,我们以它为起点,标记所有与它直接或间接相连的字母 O;最后我们遍历这个矩阵,对于每一个字母:
如果该字母被标记过,则该字母为没有被字母 X 包围的字母 O,我们将其还原为字母 O;如果该字母没有被标记过,则该字母为被字母 X 包围的字母 O,我们将其修改为字母 X。
可以使用深度优先搜索实现标记操作。在下面的代码中,我们把标记过的字母 O 修改为字母 A。
复杂度分析
时间复杂度:O(n×m),其中 n 和 m 分别为矩阵的行数和列数。深度优先搜索过程中,每一个点至多只会被标记一次。
空间复杂度:O(n×m),其中 n和 m分别为矩阵的行数和列数。主要为深度优先搜索的栈的开销。
class Solution {
int n, m;
public void solve(char[][] board) {
n = board.length;
if (n == 0) return;
m = board[0].length;
for (int i = 0; i < n; i++) {
dfs(board, i, 0);
dfs(board, i, m - 1);
}
for (int i = 1; i < m - 1; i++) {
dfs(board, 0, i);
dfs(board, n - 1, i);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (board[i][j] == 'A') {
board[i][j] = 'O';
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
public void dfs(char[][] board, int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O') return;
board[x][y] = 'A';
dfs(board, x + 1, y);
dfs(board, x - 1, y);
dfs(board, x, y + 1);
dfs(board, x, y - 1);
}
}
顺时针/逆时针打印矩阵
剑指 Offer 29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]输出:[1,2,3,6,9,8,7,4,5]
class Solution {
public int[] spiralOrder(int[][] matrix) {
if(matrix.length==0) return new int[0];
int row=matrix.length,column=matrix[0].length,start=0;
int resLength = row*column;
int[] res = new int[resLength];
int i=0;
while(i<res.length){
for(int k = start;k<column;k++){//从左至右
res[i++] = matrix[start][k];
}
if(i==resLength) break;
for(int j=start+1;j<row;j++){//从上到下
res[i++] = matrix[j][column-1];
}
if(i==resLength) break;
for(int k = column-2;k>=start;k--){
res[i++] = matrix[row-1][k];
}
if(i==resLength) break;
for(int j=row-2;j>=start+1;j--){
res[i++] = matrix[j][start];
}
start++;
column--;
row--;
}
return res;
}
}
逆时针打印矩阵
54. 螺旋矩阵:给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
示例 1:输入:
[ [ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]] 输出: [1,2,3,6,9,8,7,4,5]。
class Solution {
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> result = new ArrayList<>();
if(matrix==null||matrix.length==0) return result;
int left = 0;
int right = matrix[0].length - 1;
int top = 0;
int bottom = matrix.length - 1;
int num = matrix.length * matrix[0].length;
while (num >= 1) {
for (int i = left; i <= right && num >= 1; i++) {
result.add(matrix[top][i]);
num--;
}
top++;
for (int i = top; i <= bottom && num >= 1; i++) {
result.add(matrix[i][right]);
num--;
}
right--;
for (int i = right; i >= left && num >= 1; i--) {
result.add(matrix[bottom][i]);
num--;
}
bottom--;
for (int i = bottom; i >= top && num >= 1; i--) {
result.add(matrix[i][left]);
num--;
}
left++;
}
return result;
}
}
59. 螺旋矩阵 II
给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
示例:输入: 3输出:
[ [ 1, 2, 3 ],
[ 8, 9, 4 ],
[ 7, 6, 5 ]]
class Solution {
public int[][] generateMatrix(int n) {
int left = 0, right = n - 1, top = 0, bottom = n - 1;
int[][] res = new int[n][n];
int num = 1, tar = n * n;
while(num <= tar){
for(int i=left;i<=right;i++) res[top][i] = num++; // left to right.
top++;
for(int i=top;i<=bottom;i++) res[i][right] = num++; // top to bottom.
right--;
for(int i=right;i>=left;i--) res[bottom][i] = num++; // right to left.
bottom--;
for(int i=bottom; i>=top;i--) res[i][left] = num++; // bottom to top.
left++;
}
return res;
}
}
矩阵中的路径
剑指 Offer 12. 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = "ABCCED"输出:true
class Solution {
public boolean exist(char[][] board, String word) {
if(board.length==0) return false;
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
if(dfs(i,j,board,word,0)) return true;
}
}
return false;
}
public boolean dfs(int i,int j,char[][] board,String word,int index){
if(i<0||j<0||i>=board.length||j>=board[0].length||board[i][j]!=word.charAt(index)) return false;
if(index==word.length()-1) return true;
char tmp = board[i][j];
board[i][j]='/';
boolean res = dfs(i-1,j,board,word,index+1)||dfs(i+1,j,board,word,index+1)||dfs(i,j-1,board,word,index+1)||dfs(i,j+1,board,word,index+1);
board[i][j]=tmp;
return res;
}
}
反转字符串
写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000)
示例1: 输入 “abcd”, 输出 “dcba”。
import java.util.*;
public class Solution {
public String solve (String str) {
// write code here
char[] cs = str.toCharArray();
int n = cs.length;
for (int left=0, right=n-1;left<right;++left,--right) {
char tmp = cs[left];
cs[left] = cs[right];
cs[right] = tmp;
}
return new String(cs);
}
}
反转链表
输入一个链表,反转链表后,输出新链表的表头。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
public ListNode ReverseList(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while(cur!=null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
链表内指定区间反转
反转链表 II(LeetCode92)
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。说明:1 ≤ m ≤ n ≤ 链表长度。示例:输入: 1->2->3->4->5->NULL, m = 2, n = 4 输出: 1->4->3->2->5->NULL。
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode preStart = dummy;
ListNode start = head;
for (int i = 1; i < m; i ++ ) {
preStart = start;
start = start.next;
}
// reverse
for (int i = 0; i < n - m; i ++ ) {
ListNode temp = start.next;
start.next = temp.next;
temp.next = preStart.next;
preStart.next = temp;
}
return dummy.next;
}
}
K 个一组翻转链表
K 个一组翻转链表(LeetCode 25)
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:给你这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
说明:
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
复杂度分析:
时间复杂度:O(n),其中 n为链表的长度。head 指针会在 O(n||k)个结点上停留,每次停留需要进行一次 O(k)的翻转操作。
空间复杂度:O(1),只需要建立常数个变量。
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode hair = new ListNode(0);
hair.next = head;
ListNode pre = hair;
while (head != null) {
ListNode tail = pre;
// 查看剩余部分长度是否大于等于 k
for (int i = 0; i < k; ++i) {
tail = tail.next;
if (tail == null) {
return hair.next;
}
}
ListNode nex = tail.next;
ListNode[] reverse = myReverse(head, tail);
head = reverse[0];
tail = reverse[1];
// 把子链表重新接回原链表
pre.next = head;
tail.next = nex;
pre = tail;
head = tail.next;
}
return hair.next;
}
public ListNode[] myReverse(ListNode head, ListNode tail) {
ListNode prev = tail.next;
ListNode p = head;
while (prev != tail) {
ListNode nex = p.next;
p.next = prev;
prev = p;
p = nex;
}
return new ListNode[]{tail, head};
}
}
判断链表中是否有环
空间复杂度O(1)的解法:
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null) return false;
ListNode slow = head;
ListNode fast = head;
while(fast!=null&&fast.next!=null){
slow = slow.next;
fast = fast.next.next;
if(slow==fast) return true;
}
return false;
}
}
链表中环的入口节点
对于一个给定的链表,返回环的入口节点,如果没有环,返回null。
要求不利用额外空间。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(fast!=null&&fast.next!=null){
fast = fast.next.next;
slow= slow.next;
if(fast==slow) break;
}
if(fast==null||fast.next==null){
return null;
}
fast = head;
while(fast!=slow){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode node1 = pHead1;
ListNode node2 = pHead2;
while(node1!=node2){
node1 = node1==null?pHead2:node1.next;
node2 = node2==null?pHead1:node2.next;
}
return node2;
}
}
合并2个/k个有序链表
1.合并2个有序链表:将两个有序的链表合并为一个新链表,要求新的链表是通过拼接两个链表的节点来生成的。
循环做法:
import java.util.*;
public class Solution {
public ListNode mergeTwoLists (ListNode l1, ListNode l2) {
// write code here
ListNode head = new ListNode(0);
ListNode cur = head;
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1==null?l2:l1;
return head.next;
}
}
2.合并K个升序链表(Leetcode23)
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:[1->4->5,1->3->4,2->6]将它们合并到一个有序链表中得到:1->1->2->3->4->4->5->6
解题思路:用分治的方法进行合并。将 k个链表配对并将同一对中的链表合并;第一轮合并以后, k个链表被合并成了 k/2个链表,然后是 k/4个链表,重复这一过程,直到我们得到了最终的有序链表。
复杂度:
时间复杂度:O(kn×logk), 空间复杂度:O(logk) 。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists, 0, lists.length - 1);
}
public ListNode merge(ListNode[] lists, int l, int r) {
if (l == r) {
return lists[l];
}
if (l > r) {
return null;
}
int mid = (l + r)/2;
return mergeTwoLists(merge(lists, l, mid),
merge(lists, mid + 1, r));
}
public ListNode mergeTwoLists (ListNode l1, ListNode l2) {
ListNode head = new ListNode(0);
ListNode cur = head;
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1==null?l2:l1;
return head.next;
}
}
删除链表的倒数第n个节点
给定一个链表,删除链表的倒数第n个节点并返回链表的头指针。
例如,给出的链表为:1->2->3->4->5, n= 2,删除了链表的倒数第n个节点之后,链表变为1->2->3->5。
备注:题目保证n一定是有效的,请给出请给出时间复杂度为O(n)的算法。
import java.util.*;
public class Solution {
public ListNode removeNthFromEnd (ListNode head, int n) {
// write code here
ListNode pre = new ListNode(0);
pre.next = head;
ListNode fast = pre;
ListNode slow = pre;
while(n!=0){
fast = fast.next;
n--;
}
while(fast.next!=null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return pre.next;
}
}
单链表的选择排序
题目描述:给定一个无序单链表,实现单链表的选择排序(按升序排序)。
示例1:输入:[1,3,2,4,5],输出:{1,2,3,4,5}
import java.util.*;
public class Solution {
public ListNode sortInList (ListNode head) {
// write code here
ListNode tmp = new ListNode(0);
tmp.next = head;
ListNode sorted = tmp;
while(sorted.next!=null){
ListNode pre = sorted;
ListNode cur = sorted.next;
ListNode minPre = null;
ListNode min = null;
while(cur!=null){
if(min==null||min.val>cur.val){
minPre = pre;
min = cur;
}
cur = cur.next;
pre = pre.next;
}
minPre.next = min.next;
min.next = sorted.next;
sorted.next = min;
sorted = sorted.next;
}
return tmp.next;
}
}
实现二叉树先序,中序和后序遍历
DFS实现:
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
/**
*
* @param root TreeNode类 the root of binary tree
* @return int整型二维数组
*/
ArrayList<Integer> list = new ArrayList<>();
public int[][] threeOrders (TreeNode root) {
// write code here
dfs1(root);
int[][] res= new int[3][list.size()];
copyValue(res[0],list);
dfs2(root);
copyValue(res[1],list);
dfs3(root);
copyValue(res[2],list);
return res;
}
public void dfs1(TreeNode root){
if(root==null) return;
list.add(root.val);
dfs1(root.left);
dfs1(root.right);
}
public void dfs2(TreeNode root){
if(root==null) return;
dfs2(root.left);
list.add(root.val);
dfs2(root.right);
}
public void dfs3(TreeNode root){
if(root==null) return;
dfs3(root.left);
dfs3(root.right);
list.add(root.val);
}
public void copyValue(int[] res,ArrayList<Integer> list){
//res = new int[list.size()];
for(int i=0;i<list.size();i++){
res[i] = list.get(i);
}
list.clear();
}
}
BFS:
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* }
*/
public class Solution {
/**
*
* @param root TreeNode类 the root of binary tree
* @return int整型二维数组
*/
ArrayList<Integer> list = new ArrayList<>();
public int[][] threeOrders (TreeNode root) {
// write code here
bfs1(root);
int[][] res = new int[3][list.size()];
copy(res[0]);
bfs2(root);
copy(res[1]);
bfs3(root);
copy(res[2]);
return res;
}
public void bfs1(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
while(root!=null||!stack.isEmpty()){
while(root!=null){
stack.push(root);
list.add(root.val);
root = root.left;
}
root = stack.pop();
root = root.right;
}
}
public void bfs2(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
while(root!=null||!stack.isEmpty()){
while(root!=null){
stack.push(root);
root = root.left;
}
root = stack.pop();
list.add(root.val);
root = root.right;
}
}
public void bfs3(TreeNode root){
Stack<TreeNode> stack = new Stack<>();
while(root!=null||!stack.isEmpty()){
while(root!=null){
stack.push(root);
//list.addFirst(root.val);
list.add(root.val);
root = root.right;
}
root = stack.pop();
root = root.left;
}
Collections.reverse(list);
}
public void copy(int[] res){
for(int i=0;i<list.size();i++){
res[i] = list.get(i);
}
list.clear();
}
}
二叉搜索树的后序遍历序列
剑指 Offer 33. 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
class Solution {
public boolean verifyPostorder(int[] postorder) {
return verifyPostorder(postorder,0,postorder.length-1);
}
public boolean verifyPostorder(int[] postorder,int start ,int end){
if(start>=end) return true;
int tmp = start;
while(postorder[tmp]<postorder[end]) tmp++;
int mid = tmp;//mid属于右子树,为左右子树分界
while(postorder[tmp]>postorder[end]) tmp++;
return (tmp==end)&&verifyPostorder(postorder,start,mid-1)&&verifyPostorder(postorder,mid,end-1);
//注意,第二项不是verifyPostorder(postorder,0,mid)
}
}
二叉树的层序遍历
1.剑指 Offer 32 - I. 从上到下打印二叉树
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如: 给定二叉树: [3,9,20,null,null,15,7], 返回:[3,9,20,15,7]。
class Solution {
public int[] levelOrder(TreeNode root) {
if(root==null) return new int[0];
ArrayList<Integer> res = new ArrayList<>();
LinkedList<TreeNode> list = new LinkedList<>();
list.add(root);
while(list.size()!=0){
TreeNode node = list.removeFirst();
res.add(node.val);
if(node.left!=null) list.add(node.left);
if(node.right!=null) list.add(node.right);
}
int[] result = new int[res.size()];
for(int i=0;i<res.size();i++){
result[i] = res.get(i);
}
return result;
}
}
2.剑指 Offer 32 - II. 从上到下打印二叉树 II
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
例如: 给定二叉树: [3,9,20,null,null,15,7], 返回其层次遍历结果:[[3], [9,20], [15,7]]。
import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer>> levelOrder (TreeNode root) {
// write code here
LinkedList<TreeNode> list = new LinkedList<>();
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
if(root!=null) list.add(root);
while(list.size()!=0){
ArrayList<Integer> tmp = new ArrayList<>();
int size = list.size();
for(int i=0;i<size;i++){
TreeNode node = list.removeFirst();
tmp.add(node.val);
if(node.left!=null) list.add(node.left);
if(node.right!=null) list.add(node.right);
}
res.add(tmp);
}
return res;
}
}
二叉树的之字形层序遍历
给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)
import java.util.*;
public class Solution {
public ArrayList<ArrayList<Integer>> zigzagLevelOrder (TreeNode root) {
// write code here
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
LinkedList<TreeNode> list1 = new LinkedList<>();
LinkedList<TreeNode> list2 = new LinkedList<>();
if(root==null) return res;
list1.add(root);
boolean flag = true;
while(list1.size()!=0||list2.size()!=0){
ArrayList<Integer> tmp = new ArrayList<>();
if(flag){
while(list1.size()>0){
TreeNode node = list1.removeLast();
tmp.add(node.val);
if(node.left!=null) list2.add(node.left);
if(node.right!=null) list2.add(node.right);
}
}else{
while(list2.size()>0){
TreeNode node = list2.removeLast();
tmp.add(node.val);
if(node.right!=null) list1.add(node.right);
if(node.left!=null) list1.add(node.left);
}
}
flag = !flag;
res.add(tmp);
}
return res;
}
}
重建二叉树
剑指 Offer 07. 重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
class Solution {
Map<Integer,Integer> indexInMap = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
//if(preorder.length==0) return null; //判断二叉树是否为空,这句不加也行,可以并到后面
for(int i = 0;i<inorder.length;i++){
indexInMap.put(inorder[i],i);
}
return buildTree(preorder,0,0,inorder.length-1);
}
public TreeNode buildTree(int[] preorder,int proot,int inStart,int inEnd){//只需要inorder的坐标即可,不需要传入int[] inorder
if(inStart>inEnd) return null;
TreeNode root = new TreeNode(preorder[proot]);
if(inStart==inEnd) return root;
int inRootIndex = indexInMap.get(preorder[proot]);
root.left = buildTree(preorder,proot+1,inStart,inRootIndex-1);
//明显左侧节点的个数即为最后两项相减+1
root.right = buildTree(preorder,proot+inRootIndex-inStart+1,inRootIndex+1,inEnd);
//而root.right的proot即为proot+左节点个数+1
return root;
}
}
二叉树的最近公共祖先
1. 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root!=null){
if(root.val>p.val&&root.val>q.val) root = root.left;
else if(root.val<p.val&&root.val<q.val) root = root.right;
else return root;
}
return null;
}
}
2.(进阶版)剑指 Offer 68 - II. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null||root==p||root==q) return root;
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left==null&&right==null) return null;
if(left!=null&&right==null) return left;
if(left==null&&right!=null) return right;
return root;
}
}
在二叉树中找到两个节点的最近公共祖先(牛客版)
import java.util.*;
public class Solution {
public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
// write code here
if(root==null) return Integer.MIN_VALUE;
if(root.val==o1||root.val==o2) return root.val;
int left = lowestCommonAncestor(root.left,o1,o2);
int right = lowestCommonAncestor(root.right,o1,o2);
if(left==Integer.MIN_VALUE&&right==Integer.MIN_VALUE) return Integer.MIN_VALUE;
if(left!=Integer.MIN_VALUE&&right==Integer.MIN_VALUE) return left;
if(left==Integer.MIN_VALUE&&right!=Integer.MIN_VALUE) return right;
return root.val;
}
}
二叉树中和为某一值的路径
1.判断是否存在,给定一个二叉树和一个值sum,判断是否有从根节点到叶子节点的节点值之和等于sum 的路径。
import java.util.*;
public class Solution {
public boolean hasPathSum (TreeNode root, int sum) {
// write code here
if(root == null){
return false;
}
if(root.left == null && root.right == null){
return root.val == sum;
}
return hasPathSum(root.left, sum - root.val) ||
hasPathSum(root.right, sum - root.val);
}
}
2.返回具体路径
剑指 Offer 34. 二叉树中和为某一值的路径
输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。
class Solution {
LinkedList<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
recur(root,sum);
return res;
}
public void recur(TreeNode root,int curSum){
if(root==null) return;
path.add(root.val);
curSum -= root.val;
if(curSum==0&&root.left==null&&root.right==null)
res.add(new LinkedList(path));
recur(root.left,curSum);
recur(root.right,curSum);
path.removeLast();
//回溯语句,回到当前迭代节点的父节点时,要在path中删除当前迭代节点
}
}
二叉树的最大深度
剑指 Offer 55 - I. 二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
class Solution {
int max = 0;//利用类变量max记录当前所能得到的树的路径的最大值,其值可以随着迭代不断更新。
public int maxDepth(TreeNode root) {
dfs(root,0);//一开始层数layer=0;
return max;
}
public void dfs(TreeNode root,int layer){
if(root==null) return;
max = max>++layer?max:layer;//当前节点不为空时,则当前层的层数layer加1,并与max进行比较,取其中的较大值存入max中
dfs(root.left,layer);//layer为形参,不会随着迭代而变化,与每层迭代相对应,可用于记录到达当前层时树的路径的长度
dfs(root.right,layer);
}
}
平衡二叉树
剑指 Offer 55 - II. 平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
class Solution {
public boolean isBalanced(TreeNode root) {
return dfs(root)==-1?false:true;
}
//用left,right记录root左右子节点的深度,避免遍历root时对左右节点的深度进行重复计算。
//即用Math.max(left,right)+1;替换55_1中的Math.max(dfs(root.left),dfs(root.right))+1;
//考虑到需要同时记录各个节点的深度和其是否符合平衡性要求,这里的返回值设为int,用一个特殊值-1来表示出现不平衡的节点的情况,而不是一般采用的boolean
public int dfs(TreeNode root){
//用后序遍历的方式遍历二叉树的每个节点(从底至顶),先左子树,再右子树,最后根节点,
if(root==null) return 0;//root等于0时,该节点符合要求,返回其深度0,而不返回-1;
int left = dfs(root.left);//left最开始的取值为0,从底朝上遍历,先左子树,后右子树,最后根节点
if(left==-1) return -1;//若出现节点的深度为-1,则进行剪枝,开始向上返回,之后的迭代不再进行
int right = dfs(root.right);
if(right==-1) return -1;
return Math.abs(right-left)<2?Math.max(left,right)+1:-1;//+1不能少
//最开始计算的是左子树最左侧的一个叶节点,其左右子节点不存在,left=0,right=0,满足条件,返回该叶节点的深度max(0,0)+1=1;
}
}
二叉树的镜像
剑指 Offer 27. 二叉树的镜像
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return null;
TreeNode tmp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmp);
return root;
}
}
(牛客版本) 操作给定的二叉树,将其变换为源二叉树的镜像。
public class Solution {
public void Mirror(TreeNode root) {
if(root == null)
return;
if(root.left == null && root.right == null)
return;
TreeNode pTemp = root.left;
root.left = root.right;
root.right = pTemp;
if(root.left != null)
Mirror(root.left);
if(root.right != null)
Mirror(root.right);
}
}
对称的二叉树
剑指 Offer 28. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;//空树也是镜像的
return dfs(root.left,root.right);
}
public boolean dfs(TreeNode left,TreeNode right){
if(left==null&&right==null) return true;
if(left==null||right==null) return false;//结合上面一条if,此处为if为被比较的两个节点有一个为空时
if(left.val!=right.val) return false;
return dfs(left.left,right.right)&&dfs(left.right,right.left);
}
}
二叉搜索树的第k大/小节点
1.剑指 Offer 54. 二叉搜索树的第k大节点(寻找的是值时)
给定一棵二叉搜索树,请找出其中第k大的节点。
class Solution {
int count=0, res=0;//形参k不能随着dfs的迭代而不断变化,为了记录迭代进程和结果,引入类变量count和res。
public int kthLargest(TreeNode root, int k) {
count=k;//利用形参值k对类变量count进行初始化
dfs(root);//这里不要引入形参k,dfs中直接使用的是初始值为k的类变量count
return res;//题目规定:1≤k≤二叉搜索树元素个数,说明root不为空,若root为空,此时返回res初始值0;
}
public void dfs(TreeNode root){
if(root==null||count==0) return;//当root为空或者已经找到了res时,直接返回
dfs(root.right);
if(--count==0){//先--,再判断
res = root.val;
return;//这里的return可以避免之后的无效迭代dfs(root.left);
}
dfs(root.left);
}
}
2.二叉搜索树的第k小的节点(寻找的是节点)
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
public class Solution {
int count=0;
TreeNode res=null;
TreeNode KthNode(TreeNode pRoot, int k){
count=k;//利用形参值k对类变量count进行初始化
dfs(pRoot);//这里不要引入形参k,dfs中直接使用的是初始值为k的类变量count
return res;//题目规定:1≤k≤二叉搜索树元素个数,说明root不为空,若root为空,此时返回res初始值0;
}
public void dfs(TreeNode root){
if(root==null||count==0) return;//当root为空或者已经找到了res时,直接返回
dfs(root.left);
if(--count==0){//先--,再判断
res = root;
return;//这里的return可以避免之后的无效迭代dfs(root.left);
}
dfs(root.right);
}
}
二分
二分查找法:二分查找是一种在有序数组中查找某一特定元素的搜索算法。
搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜素过程结束;
如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。
如果在某一步骤数组已经为空,则表示找不到指定的元素。这种搜索算法每一次比较都使搜索范围缩小一半,其时间复杂度是O(logN)。
注意,如果数组中存在多个相同的数字,在寻找该数字时,二分查找法返回的不一定是该数字的第一个坐标。
public class BinarySerach {
//如果待搜索元素在排序数组arr中,则返回其坐标,否则返回-1;
public static int binarySerach(int[] arr,int num) {
int start = 0, end=arr.length-1;
int mid = start+(end-start)/2;
while(start<=end) {//包括=
if(arr[mid]==num) return mid;//这里有return语句,否则此时start和end不变,则后续mid不变,会陷入死循环
else if(arr[mid]>num) end =mid-1;
else start = mid+1;
mid = start+(end-start)/2;
}
return -1;
}
}
堆排序
堆的结构可以分为大根堆和小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序。(若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1)的结点数都达到最大个数,第 h层所有的结点都连续集中在最左边,这就是完全二叉树)。
大根堆:每个结点的值都大于或等于其左孩子和右孩子结点的值;小根堆:每个结点的值都小于或等于其左孩子和右孩子结点的值。
还有一个基本概念:查找数组中某个数的父结点和左右孩子结点,比如已知索引为i的数,那么:
1.父结点索引:(i-1)/2(这里计算机中的除以2,省略掉小数);
2.左孩子索引:2i+1; 3.右孩子索引:2i+2;
堆排序的时间复杂度O(N*logN),额外空间复杂度O(1),是一个不稳定性的排序,且升序用大根堆,降序就用小根堆。
1、将待排序数组构造成一个大根堆(元素上升,新插入的数据与其父结点比较)
2、将顶端的数(最大值)与最后一位数交换,固定该最大值,然后将剩余的数再构造成一个大根堆(元素下降),重复执行上面的操作,最终会得到有序数组
public class HeapSort {
public static void heapSort(int[] arr) {
heapInsert(arr,1);//构造大顶堆
sort(arr,arr.length-1);
}
//构造大根堆(通过新插入的数上升)
public static void heapInsert(int[] arr,int index) {
if(index == arr.length) return;
int cur = index;//cur的值在变化,这里用index保存坐标
int father=(cur-1)/2;
while(cur!=0&&arr[cur]>arr[father]) {
//每次新插入的数据都与其父结点进行比较,如果插入的数比父结点大,则与父结点交换,否则一直向上交换,直到小于等于父结点,或者来到了顶端
swap(arr,cur,father);
cur = father;
father = (cur-1)/2;
}
heapInsert(arr,index+1);
}
//将顶端的数(最大值)与最后一位数交换,固定该最大值,然后将剩余的数构造成大根堆(通过顶端的数下降)
public static void sort(int[] arr,int end) {//这里不用传入start值,因为每次都是顶端的数字下降
if(end==0) return;
swap(arr,0,end);
int cur = 0;
int left = cur*2+1;
int right = cur*2+2;
int maxIndex=0;
while(left<end) {//下一级存在,这里不能用cur!=end-1判断,cur满足坐标限制,left不一定满足坐标限制
if(right<end) maxIndex = arr[left]>arr[right]?left:right;
else maxIndex= left;
if(arr[cur]>=arr[maxIndex]) break;
swap(arr,cur,maxIndex);
cur = maxIndex;
left = cur*2+1;
right = cur*2+2;
}
sort(arr,end-1);
}
public static void swap(int[] arr,int a,int b) {
int tmp = arr[a];
arr[a]=arr[b];
arr[b]=tmp;
}
public static void main(String[] args) {
int[] a = {6,1,-3,7,-7,2,4,12,8,9,5,0};
heapSort(a);
for(int i:a) {
System.out.print(i+"\t");
}
}
}
归并排序
归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
1.首先考虑下如何将将二个有序数列合并。
这个非常简单,只要从比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。合并有序数列的效率是比较高的,可以达到O(n)。
2.解决了上面的合并有序数列问题,再来看归并排序。
基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。
那么如何让这二组组内数据有序?
可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。
分阶段可以理解为就是递归拆分子序列的过程,递归深度为log n。
这样通过先递归的分解数列,再合并数列就完成了归并排序。
归并排序的效率是比较高的,
设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(NlogN)。
因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(NlogN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。
import java.util.Arrays;
public class MergeSort {
public static void sort(int[] arr) {
int[] temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的数组,避免递归中频繁开辟空间
sort(arr,0,arr.length-1,temp);
}
public static void sort(int[] arr,int start,int end,int[] temp) {
if(start>=end) return;
//int mid = (start+end)/2;这种写法在start和end较大时,相加可能会出现溢出,数据较小时,这种写法和下面的写法等价
int mid =start+(end-start)/2;
sort(arr,start,mid,temp);//左边归并排序,使得左子序列有序
sort(arr,mid+1,end,temp);//右边归并排序,使得右子序列有序
merge(arr,start,mid,end,temp);//将两个有序子数组合并操作
}
//将两个有序数列arr[start,...,mid]和arr[mid+1,...,end]合并
public static void merge(int[] arr,int start,int mid,int end,int[] temp) {
int i =start;//左序列指针
int j =mid+1;//右序列指针
int k =start;//临时数组指针
while(i<=mid&&j<=end) {
if(arr[i]<arr[j])//从小到大排列
temp[k++]=arr[i++];
else
temp[k++]=arr[j++];
}
while(i<=mid){//如果左边剩余,将左边剩余元素填充进temp中
temp[k++] = arr[i++];
}
while(j<=end) {//如果右边剩余,将右序列剩余元素填充进temp中
temp[k++] = arr[j++];
}
while(start<=end) {
arr[start] = temp[start];//这里不能写作arr[start++]=tmp[start++];这样就++了两次了
start++;
//将temp中当前操作的部分元素(已排序)拷贝到原数组中,覆盖arr[left,...,right]这部分数据。
}
}
public static void main(String[] args) {
int[] a = new int[]{3,22,5,6,1,8,10,34,5,2};
sort(a);
System.out.println(Arrays.toString(a));
}
}
快排
(1)首先设定一个分界值key,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
值得注意的是,快速排序不是一种稳定的排序算法,也就是说,多个相同的值的相对位置也许会在算法结束时产生变动
public void qsort(int arr[],int start,int end) {
if(start>=end) {
return;
}
int i =start;
int j =end;
int key = arr[i];
//数组中比key小的放在左边,比key大或等于的放在右边
while(i<j) {
//一定是j--在前,i++在后,否则会出现错误
while((i<j)&&(arr[j]>=key)){//这里带等号是保证大于等于key的项不需要移动,在右侧就好
j--;
}
while((i<j)&&(arr[i]<=key)) {
//这里带等号是为了保证第0项不移动,后续方便arr[i]与arr[start]=key交换
i++;
}
if(i<j) {
int temp =arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
//因为是先j--,再i++,所以,此时arr[i]<=arr[start]=key 即保证后面将小于基准的值交换到头部
//最后将基准key与i和j相等位置的数字交换
arr[start] = arr[i];
arr[i]=key;//交换使得key分界线回到中间
qsort(arr,start,i-1);//递归调用左半数组
qsort(arr,i+1,end); //递归调用右半数组
}
网络延迟时间
网络延迟时间(Leetcode 743)
有 N 个网络节点,标记为 1 到 N。
给定一个列表 times,表示信号经过有向边的传递时间。 times[i] = (u, v, w),其中 u 是源节点,v 是目标节点, w 是一个信号从源节点传递到目标节点的时间。
现在,我们从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1。
times = [[2,1,1],[2,3,1],[3,4,1]], N = 4, K = 2 输出:2
最短路算法
SPFA是一种用队列优化的B-F算法,在稀疏图中,采用类似邻接链表储存比较节省空间。也需要用到dis和vis数组,开N+1,初始化也要看情况
【算法思想】
初始时,只有把起点放入队列中。
遍历与起点相连的边,如果可以松弛就更新距离dis[],然后判断如果这个点没有在队列中就入队标记。
出队队首,取消标记,循环2-3步,直至队为空。
所有能更新的点都更新完毕,dis[]数组中的距离就是,起点到其他点的最短距离。
// SPFA:用邻接表写
public int networkDelayTime(int[][] times, int N, int K) {
Map<Integer, List<int[]>> map = new HashMap<>();
// 构建邻接表
for (int[] arr : times) {
List<int[]> list = map.getOrDefault(arr[0], new ArrayList<>());
list.add(new int[]{arr[1], arr[2]});
map.put(arr[0], list);
}
// 初始化dis数组和vis数组
int[] dis = new int[N + 1];
int INF = 0x3f3f3f3f;
Arrays.fill(dis, INF);
boolean[] vis = new boolean[N + 1];
dis[K] = dis[0] = 0;
Queue<Integer> queue = new LinkedList<>();
queue.offer(K);
while (!queue.isEmpty()) {
// 取出队首节点
Integer poll = queue.poll();
// 可以重复入队
vis[poll] = false;
// 遍历起点的邻居,更新距离
List<int[]> list = map.getOrDefault(poll, Collections.emptyList());
for (int[] arr : list) {
int next = arr[0];
// 如果没更新过,或者需要更新距离()
if (dis[next] == INF || dis[next] > dis[poll] + arr[1]) {
// 更新距离
dis[next] = dis[poll] + arr[1];
// 如果队列中没有,就不需要再次入队了 (那么判断入度可以在这里做文章)
if (!vis[next]) {
vis[next] = true;
queue.offer(next);
}
}
}
}
int res = Arrays.stream(dis).max().getAsInt();
return res == INF ? -1 : res;
}
并查集–朋友圈
朋友圈 (leetcode547)
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例:输入:
[[1,1,0],
[1,1,0],
[0,0,1]]输出:2 解释:已知学生 0 和学生 1 互为朋友,他们在一个朋友圈。第2个学生自己在一个朋友圈。所以返回 2 。
class Solution {
public int findCircleNum(int[][] M) {
int n = M.length;
UF uf = new UF(n);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (M[i][j] == 1)
uf.union(i, j);
}
}
return uf.count();
}
class UF {
// 连通分量个数
private int count;
// 存储一棵树
private int[] parent;
// 记录树的“重量”
private int[] size;
public UF(int n) {
this.count = n;
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ)
return;
// 小树接到大树下面,较平衡
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
count--;
}
public boolean connected(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
return rootP == rootQ;
}
private int find(int x) {
while (parent[x] != x) {
// 进行路径压缩
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
public int count() {
return count;
}
}
}
背包问题
问题描述:
一个背包的总容量为V,现在有N类物品,第i类物品的重量为weight[i],价值为value[i]
那么往该背包里装东西,怎样装才能使得最终包内物品的总价值最大。这里装物品主要由三种装法:
1、0-1背包:每类物品最多只能装一次
2、多重背包:每类物品都有个数限制,第i类物品最多可以装num[i]次
3、完全背包:每类物品可以无限次装进包内
一、0—1背包
思路分析:
0-1背包问题主要涉及到两个问题的求解
a)求解背包所含物品的最大值:
利用动态规划求最优值的方法。假设用dp[N][V]来存储中间状态值,dp[i][j]表示前i件物品能装入容量为j的背包中的物品价值总和的最大值(注意是最大值),则我们最终只需求知dp[i=N][j=V]的值,即为题目所求。
现在考虑动态规划数组dp[i][j]的状态转移方程:
/假设我们已经求出前i-1件物品装入容量j的背包的价值总和最大值为dp[i-1][j],固定容量j的值不变,则对第i件物品的装法讨论如下:
首先第i件物品的重量weight[i]必须小于等于容量j才行,即
1、若weight[i]>j,则第i件物品肯定不能装入容量为j的背包,此时dp[i][j]=dp[i-1][j]
2、若weight[i]<=j,则首先明确的是这件物品是可以装入容量为j的背包的,那么如果我们将该物品装入,则有
dp[i][j]=dp[i-1][j-weight[i]]+value[i]
随之而来的问题是我们要判断第i件物品装到容量为j的背包后,背包内的总价值是否是最大?其实很好判断,即如果装了第i件物品后的总价值dp[i-1][j-weight[i]]+value[i]>没装之前的总价值最大值dp[i-1][j],则肯是最大的;反之则说明第i件物品不必装入容量为j的背包(装了之后总价值反而变小,那么肯定就不需要装嘛)
故,状态转移方程如下:
dp[i][j] = (dp[i-1][j] > (dp[i-1][j-weight[i]]+value[i]))? dp[i-1][j]:(dp[i-1][j-weight[i]]+value[i])
注意:这里的前i件物品是给定次序的
b)求出背包中装入物品的编号
这里我们采用逆推的思路来处理,如果对于dp[i][j]>dp[i-1][j],则说明第i个物品肯定被放入了背包,此时我们再考察dp[i-1][j-weight[i]]的编号就可以了。
import java.util.*;
public class BeiBao {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int size = sc.nextInt();
int n = sc.nextInt();
int[] v = new int[n];
int[] value = new int[n];
for(int i=0;i<n;i++) {
v[i]=sc.nextInt();//size
}
for(int i=0;i<n;i++) {
value[i]=sc.nextInt();//value
}
System.out.println(bag(size,n,v,value));
}
public static int bag(int size,int n,int[] v,int[] value ) {
int[] dp = new int[size+1];
for(int i=0;i<n;i++) {
for(int j=size;j>=v[i];j--) {
dp[j] = Math.max(dp[j-v[i]]+value[i], dp[j]);
}
}
return dp[size];
}
}