目录
10、实现 strStr() 即Java中的indexOf() (2020/11/22) 待优化
13、寻找旋转排序数组中的最小值 -- 二分法(2020/12/2)
1、两数之和
基础版
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0;i < nums.length;i++){
map.put(nums[i],i);
}
for (int i = 0;i < nums.length;i++){
int temp = target - nums[i];
if(map.containsKey(temp) && map.get(temp) != i){
return new int[] {i,map.get(temp)};
}
}
throw new IllegalArgumentException("No two sum solution");
}
}
两数之和(二)
/**
* 给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target
* 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
*
* **重点:升序、不可重复使用元素
* 输入:numbers = [2,7,11,15], target = 9
* 输出:[1,2]
*/
public class TwoSum {
/**
* 时间复杂度O(n) 空间复杂度O(n)
* 不满足要求: 不可重复使用元素
*/
public int[] twoSum(int[] numbers, int target) {
if (numbers==null || numbers.length==0) return new int[0];
HashMap<Integer, Integer> map = new HashMap<>(); //K target-num[i] V index
for(int i=0;i<numbers.length;i++){
int num = numbers[i];
if(map.containsKey(num)) return new int[]{map.get(num)+1,i+1};
map.put(target-num,i);
}
return new int[0];
}
/**
* 二分法 升序 时间复杂度O(nlogn) n->for(~;~;~) logn->while(left<=right) 空间:O(1)
*/
public int[] twoSum2(int[] numbers, int target){
if (numbers==null || numbers.length==0) return new int[0];
for(int i=0;i<numbers.length-1;i++){
int small = numbers[i];
int sub = target - small;
int left = i+1;
int right = numbers.length-1; //java.lang.ArrayIndexOutOfBoundsException
while (left<=right){
int mid = left + ((right-left) >> 1);
if(sub==numbers[mid]){
return new int[]{i+1,mid+1};
}else if(sub>numbers[mid]){ //当前值小于差值 mid >>
left = mid+1;
}else {
right = mid-1;
}
}
}
return new int[0];
}
/**
* 双指针 [length-1]+[0] 根据和大小判断移动哪个指针
* 时间复杂度:O(n) 空间复杂度O(1)
*/
public int[] twoSum3(int[] numbers, int target) {
if (numbers == null || numbers.length == 0) return new int[0];
int left = 0;
int right = numbers.length-1;
while (left<=right){
int add = numbers[right]+numbers[left];
if (add==target){
return new int[]{left+1,right+1};
}else if(add<target){
left++;
}else {
right--;
}
}
return new int[0];
}
}
2、整数反转
class Solution {
public int reverse(int x) {
int res;
int temp = 0 ;
while(x!=0){
temp = temp*10+x%10;
if((temp>2147483647 && x/10==0) || (temp>214748364 && x>7)) {
return 0;
}
if((temp<-2147483648 && x/10==0) || (temp<-214748364 && x<-8)) {
return 0;
}
x = x/10;
}
res = temp;
return res;
}
}
3、回文数
class Solution {
public boolean isPalindrome(int x) {
if(x<0){
return false;
}else if(x==0){
return true;
}else{
byte[] bs = String.valueOf(x).getBytes();
for (int i = 0; i < bs.length; i++) {
if(!(bs[i]==bs[bs.length-i-1])) {
return false;
}
}
}
return true;
}
}
4、罗马数字转整数
class Solution {
public int romanToInt(String s) {
char[] array = s.toCharArray();
int res = 0 ;
for (int i=0;i<array.length-1;i++) {
if(getNum(array[i])-getNum(array[i+1])<0) {
res-=getNum(array[i]);
}else{
res+=getNum(array[i]);
}
}
res+=getNum(array[array.length-1]);
return res;
}
public int getNum(char str){
switch(str){
case 'I':
return 1;
case 'V':
return 5;
case 'X':
return 10;
case 'L':
return 50;
case 'C':
return 100;
case 'D':
return 500;
case 'M':
return 1000;
default:
return 0;
}
}
}
5、最大公共前缀
(1)横向扫描
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length==0) {
return "";
}else if(strs.length==1){
return strs[0];
}
String prefix=strs[0];
for(int i=1;i<strs.length;i++) {
int length = Math.min(prefix.length(), strs[i].length());
int index=0;
while(index<length && prefix.charAt(index)==strs[i].charAt(index)) {
index++;
}
prefix = prefix.substring(0,index);
if(prefix.length()==0) {break;};
}
return prefix;
}
}
(2)纵向扫描
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length==0) {
return "";
}else if(strs.length==1){
return strs[0] ;
}
int str0len = strs[0].length() ;
int index = 0;
String prefix = strs[0] ;
for(int i=0;i<str0len;i++) {
for (int j = 0; j < strs.length; j++) {
if(i==strs[j].length() || strs[j].charAt(i) != strs[0].charAt(i)){
return strs[0].substring(0,i);
}
}
}
return prefix;
}
}
6、有效的括号(2020/11/3)
class Solution {
public boolean isValid(String s) {
int length = s.length();
if(length %2 ==1) return false;
Map<Character, Character> pairs = new HashMap<Character, Character>() {
{
put(')','(');
put('}','{');
put(']','[');
}
};
//双端队列:具有队列和栈的性质的数据结构
//中心思想:只包含括号,所以右元素出现,stack top必定是左元素(中间匹配的会出栈)
Deque<Character> stack = new LinkedList<Character>();
for(char ch : s.toCharArray()){
if(pairs.containsKey(ch)){
if (stack.isEmpty() || stack.peek() != pairs.get(ch)) return false;
stack.pop();
}else{
stack.push(ch);
}
}
//若全部匹配最后会全部出栈
return stack.isEmpty();
}
}
7、合并两个有序链表 (2020/11/11)
方法一: 递归
public class Solution {
public static void main(String[] args) {
ListNode listNodes = mergeTwoLists(
new ListNode(1,new ListNode(5,new ListNode(10))), //l1
new ListNode(2,new ListNode(4,new ListNode(5)))); //l2
System.out.println(listNodes);
}
//递归 tips: 前提 两个有序链表 遇到l1或l2有null后,依次return,最后返回的是第一次对比的l1或l2
public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
} else if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1; //最后返回
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
public static class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
@Override
public String toString() {
return "ListNode{" +
"val=" + val +
", next=" + next +
'}';
}
}
}
方法二:迭代
//迭代 即:一个列表保存合并后的数据
// 时间复杂度 O(n+m) 空间复杂度 O(1)
public static ListNode mergeTwoListsByDiedai(ListNode l1,ListNode l2){
ListNode res = new ListNode(-1);
ListNode prev = res ; //指针 弱引用,与res一个地址
while (l1.next !=null && l2.next != null){
if(l1.val < l2.val){
prev.next = l1;
l1 = l1.next;
}else{
prev.next = l2;
l2 = l2.next;
}
prev = prev.next; //指针后移(即:prev指向当前prev/res的next,res始终不变)
}
//当迭代到一个listnode为空,另一个可能没迭代完成
prev.next = l1==null?l2:l1;
return res.next;
}
8、删除排序数组中的重复项(2020/11/12)
/**
* 题目:给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
* 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
* tips:给两个指针,一个快指针、一个慢指针 i慢 j快
* 当array[i] == array[j]时,i停滞在重复数据的下标,!= 时 自然把重复的array[i]覆盖上array[j]的值
* 双指针法 时间复杂度 O(n) 空间O(1)
*/
public int removeDuplicates(int[] nums) {
if (nums == null || nums.length == 0) return 0;
int i = 0;
for(int j = 1;j<nums.length;j++){ //不满足for(condition)不执行for
if(nums[i] != nums[j] ){
i++;
nums[i] = nums[j] ;
}
}
return i+1; // +1 是加nums[0]元素
}
9、移除元素 (2020/11/20)
/**
* 题目:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
* 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
*/
public static int removeElement(int[] nums, int val) {
int i = 0;
//快慢双指针法,找到等于val值时,慢指针不动,不等于时,慢指针位置赋值
for(int j = 0; j<nums.length; j++){
if(nums[j] != val){
nums[i] = nums[j];
i++;
}
}
return i;
}
10、实现 strStr() 即Java中的indexOf() (2020/11/22) 待优化
//给定一个 haystack 字符串和一个 needle 字符串,在 haystack字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
/**
* method One:子串逐一比较 - 线性时间复杂度 窗口滑动比较
*/
public static int strStrMethod(String haystack, String needle) {
System.out.println("预期值:"+haystack.indexOf(needle));
int sourceCount = haystack.length(),targetCount = needle.length();
// sourceCount<targetCount时:for循环不成立,不执行
// j++ 和 ++j {}内j值相等
for (int j=0;j<sourceCount+1-targetCount;++j){
if (needle.equals(haystack.substring(j,j+targetCount))) return j;
}
return -1;
}
11、搜索插入位置(2020/11/23)
方法一:逐一比较法
//给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。 如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
public static int SearchInsertMethod(int[] nums,int target){
for(int i=0;i<nums.length;i++){
if(nums[i] == target || nums[i] > target) return i;
if (i == nums.length-1) return i+1;
}
return 0;
}
方法二:二分查找
public static int twoDvideMeth(int[] nums,int target){
int res = nums.length,left=0,right=nums.length-1; //res是最终答案,先默认是数组内没有该元素
while (left <= right){
int mid = ((right-left) >> 1) +left; //中间值
if(target <= nums[mid]){
res = mid; //因为target <= nums[mid],所以每次设置res是最大的mid,直到遍历结束
right = mid-1;
}else{
left = mid+1;
}
}
return res;
}
12、搜索旋转排序数组 --二分法(2020/11/27)
//Topic: 给你一个整数数组 nums ,和一个整数 target.请你在数组中搜索 target ,
// 如果数组中存在这个目标值,则返回它的索引,否则返回 -1
public static int search(int[] num,int target){
if(num == null || num.length == 0) return -1;
int start = 0;
int end = num.length-1;
int mid ;
while(start +1 < end){
mid = start + ((end-start) >>1) ; // >> 运算顺序和加法一个等级
if(num[mid] == target){
return mid;
}
if (num[start] < num[mid]){//start-mid排序正常
//upper
if(target >= num[start] && target<=num[mid]){
end = mid;
}else{
start = mid;
}
}else{
// lower start-mid 是旋转数。包含两个分段排序数组,mid-end是正常排序
// 还会继续把旋转数组分成 旋转数组+sort array 以此类推
if (target >= num[mid] && target<=num[end]) {
start = mid;
}else{
end = mid;
}
}
}
if(num[start] == target) return start;
if(num[end] == target) return end;
return -1;
}
13、寻找旋转排序数组中的最小值 -- 二分法(2020/12/2)
// 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
// [3,4,5,1,2]
// 虽然旋转了,但是仍然是两段式有序。我们采用二分生序对比,最终会归结到最小值那一段的索引0,1
public int minArray(int[] numbers) {
if(numbers==null || numbers.length==0){
return 0;
}
int left=0;
int right = numbers.length-1;
while(left < right) {
int pivot = left;
if(numbers[right] > numbers[pivot]) {
right = pivot;
}
else if(numbers[right] < numbers[pivot]) {
left = pivot+1;
}
else {
right -= 1;
}
}
return numbers[left];
// 遍历
//for(int i=0;i<numbers.length-1;i++){
// if(numbers[i] > numbers[i+1]) {
// return numbers[i+1];
// }
//}
//return numbers[0];
}
14、外观数列
/**
* Date: 2020/12/25
* Topic: 外观数列:是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述
* 1. 1
* 2. 11
* 3. 21
* 第一项是数字 1
* 描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 "11"
* 描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 "21"
*/
public static String countAndSay(int n){ //n 迭代次数
String result = "1"; //初始值1
for(int i=1; i<n; i++){
result = intToApperArray(result);
}
return result;
}
public static String intToApperArray(String n) {//随机数字转外观数列
char[] array = n.toCharArray();
int count = 0; // current exist count char
StringBuilder result = new StringBuilder(); // final result
for(int i = 0; i<array.length; i++){
if(i != 0 && array[i] != array[i-1]){ // array[now] != array[now-1]
result.append(count).append(array[i-1]) ;
count = 1;
}else {
count++;
}
if(i == array.length-1) result.append(count).append(array[i]) ; //last node
}
return result.toString();
}
15、最大子序和
/**
* Created by xinBa.
* User: 辛聪明
* Date: 2020/12/25
* Topic: 最大子序和
* 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
* 输入: [-2,1,-3,4,-1,2,1,-5,4]
* 输出: 6
* 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
*/
/**
* 方法一: 动态规划:动态规划算法用于求解具有某种最优性质的问题
* 算法思想:将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解
* 本题思想:变量pre存放 当前最大子序和 maxAns存放 最大子序和
* 复杂度: 时间复杂度:O(n):其中 n 为 nums 数组的长度,我们只需要遍历一遍数组即可求得答案。
* 空间复杂度:O(1):我们只需要常数空间存放若干变量
*/
static int maxSubArray(int[] nums){
if(nums == null || nums.length==0) return 0;
int pre = 0, maxAns = nums[0];
for (int x : nums) {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
/**
* 方法二:分治法 :[分而治之]把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……
* 直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并.
* 步骤:1. 划分步 2. 治理步 3. 组合步
* 复杂度:时间复杂度:O(n)
* 空间复杂度:O(logn)
*/
static int DivideAndConquer(int[] nums){
return getInfo(nums,0,nums.length-1).mSum;
}
static class Status{
int lSum,rSum,iSum,mSum;
public Status(int lSum, int rSum, int iSum, int mSum) {
this.lSum = lSum; //左端点的最大子段和
this.rSum = rSum; //右端点的最大子段和
this.iSum = iSum; //区间和
this.mSum = mSum; //整个最大子段和
}
}
static Status getInfo(int[] nums,int left,int right){
if (left == right) {
return new Status(nums[left],nums[left],nums[left],nums[left]); //递归遍历到最后左右相等
}
int mid = left + ((right-left) >> 1) ;
Status s1 = getInfo(nums,left,mid);
Status s2 = getInfo(nums,mid+1,right);
return option(s1,s2);
}
static Status option(Status s1,Status s2){
int lSum = Math.max(s1.lSum,s1.iSum+s2.lSum);
int rSum = Math.max(s2.rSum,s1.rSum+s2.iSum);
int iSum = s1.iSum + s2.iSum;
int mSum = Math.max(Math.max(s1.mSum,s2.mSum),s1.rSum + s2.lSum);
return new Status(lSum,rSum,iSum,mSum);
}
16、最后一个单词的长度
public static int lastWordMethod1(String word){
if(word == null || word.length()==0) return 0;
String[] words = word.split(" ");
return words[words.length-1].length();
}
public static int lastWordMethod2(String word){
if(word == null || word.length()==0) return 0;
String trim = word.trim();
int i = trim.lastIndexOf(" ");
if(i>0){
int length = trim.substring(i).length()-1;
return length<0?0:length;
}else{
return trim.length();
}
}
public static int lastWordMethod3(String word){
if(word == null || word.length()==0) return 0;
int res = 0;
int temp = word.length()-1;
while (temp>=0 && word.charAt(temp) == ' ') temp--;
while (temp>=0 && word.charAt(temp) != ' ') {
temp -- ;
res ++ ;
}
return res;
}
17、数组加一
/**
* Created by xinBa.
* User: 辛聪明
* Date: 2021/2/18
* topic:给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
* 输入:digits = [1,2,3]
* 输出:[1,2,4]
* 1 <= input.length <= 100
* 0 <= input[i] <= 9
*/
if(input == null || input.length==0) return null;
int len = input.length-1;
input[len] = input[len]+1;
while (len>0 && input[len] > 9){ //逐位+1
input[len] = input[len]-10;
input[len-1] = input[len-1]+1;
len--;
}
if(input[0]>9){ //如果存在10的情况
input = new int[input.length+1];
input[0]=1;
}
return input;
18、二进制相加
/**
* Created by xinBa.
* User: 辛聪明
* Date: 2021/2/19
* Topic: 二进制数相加
* 输入: a = "11", b = "1"
* 输出: "100"
*/
/**
* 方法一:自带高精度运算 缺陷: 如果字符串超过 33 位,不能转化为 Integer 报错NumberFormatException
* 如果 a 的位数是 n,b 的位数为 m,这个算法的渐进时间复杂度为 O(n + m)
*/
static String addBinary(String a,String b){
int aBinary = Integer.parseInt(a,2);
int bBinary = Integer.parseInt(b,2);
return Integer.toBinaryString(aBinary+bBinary);
}
/**
* 方法二:「列竖式」的方法,末尾对齐,逐位相加。在十进制的计算中「逢十进一」,二进制中我们需要「逢二进一」
*/
static String addBinary2(String a, String b) {
StringBuffer ans = new StringBuffer();
int n = Math.max(a.length(), b.length()), carry = 0;//carry 存放进位值 即 1 or 0
for (int i = 0; i < n; ++i) {
/**
* +'0' -'0' 都是转换操作
* eg:1+'0' 是隐式转换 '0'自动向上转换成 int类型 49 so: 1+'0'=49 -> int类型
* '1'-'0' = 1 (int) 任何char类型 -'0' 都输出 char的对等值 int 类型数值
* (char)(1+'0') 是强制转换 int -> char 輸出是1
*/
carry += i < a.length() ? (a.charAt(a.length() - 1 - i) - '0') : 0;//等值转换int
carry += i < b.length() ? (b.charAt(b.length() - 1 - i) - '0') : 0;
ans.append((char) (carry % 2 + '0'));
carry /= 2; //逢二进一,除以二
}
if (carry > 0) {//最前位存在进位情况
ans.append('1');
}
ans.reverse();//StringBuffer append 倒置存放
return ans.toString();
}
static String addBinary3(String a, String b){
StringBuffer result = new StringBuffer();
int length = Math.max(a.length(),b.length()), carry=0;
for (int i=0;i<length;i++){
carry += i<a.length() ? Integer.parseInt(a.charAt(a.length()-1-i)+"") : 0;
carry += i<b.length() ? Integer.parseInt(b.charAt(b.length()-1-i)+"") : 0;
result.append((char)(carry%2 + 48));
carry/=2;
}
if(carry>0) result.append('1');
return result.reverse().toString();
}
19、求x的整数平方根(二分、袖珍计算器)
* Topic: 计算并返回 x 的平方根,其中 x 是非负整数
* 输入: 8
* 输出: 2
//二分法
static int mySqrt(int input){
if(input<1) return 0;
int left = 1;
int right = input;
while(left <= right){
int mid = ((right - left) >> 1) + left;
if((long)mid*mid>input) {
right = mid;
}else {
if(((long)(mid+1)*(mid+1)>input) || (long)mid*mid==input) return mid;
left = mid;
}
}
return -1;
}
/**
* 袖珍计算器算法: 用指数函数 exp 和对数函数 ln 代替平方根函数的方法。
* exp(x) = (e^(lnx))^0.5 = e^(0.5*lnx)
*/
static int mySqrt2(int x) {
if (x == 0) return 0;
int ans = (int) Math.exp(Math.log(x) * 0.5);
//而指数函数和对数函数的参数和返回值均为浮点数,因此运算过程中会存在误差。
//所以得到结果的整数部分ans后,我们应当找出ans与ans+1 中哪一个是真正的答案
return (long) (ans + 1) * (ans + 1) <= x ? ans + 1 : ans;
}
20、小岛问题(广度优先)
/**
* Created by xinBa.
* User: 辛聪明
* Date: 2021/3/16
* 小岛问题:广度优先算法
*
* 输入:grid = [
* ["1","1","0","0","0"],
* ["1","1","0","0","0"],
* ["0","0","1","0","0"],
* ["0","0","0","1","1"]
* ]
* 输出:3 当1的上下左右都不是0的时候,此岛屿孤立为一个岛,求共有多少个岛屿
*
*/
public class IslandCount {
public static void main(String[] args) {
char[][] chars = {{'1','1','0','0','0'},{'1','1','0','0','0'},{'0','0','1','0','0'},{'0','0','0','1','1'}};
char[][] char2 = {{'1','1','1','1','0'},{'1','1','0','1','0'},{'1','1','0','0','0'},{'0','0','0','0','0'}};
IslandCount islands = new IslandCount();
System.out.println(islands.numIslands(char2));
}
public int numIslands(char[][] grid) {
boolean[][] rows = new boolean[grid.length][grid[0].length]; //用于记录是否已经访问
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' && !rows[i][j]) { //岛屿
execute(grid,i,j,rows);
count ++;
}
}
}
return count;
}
private void execute(char[][] grid, int i, int j,boolean[][] row){
if(grid[i][j] == '1') { //岛屿
row[i][j] = true;
int[] x = {0,0,-1,1}; //记录上下左右的行在当前i的什么位置
int[] y = {1,-1,0,0}; //记录上下左右的列在当前i的什么位置
Queue<Integer> qx = new LinkedList<Integer>(); //记录行队列
Queue<Integer> qy = new LinkedList<Integer>(); //记录列队列
qx.offer(i);
qy.offer(j);
while (!qx.isEmpty()){
int curX = qx.poll();
int curY = qy.poll();
for (int k=0;k<4;k++){
int m = curX+x[k], n = curY+y[k];
if(m>=0 && n>=0 && m<grid.length && n<grid[0].length && !row[m][n]){
if(grid[m][n] == '1'){
qx.offer(m);
qy.offer(n);
row[m][n] = true;
}
}
}
}
}
}
}
21、有效的数独
/**
* Created by xinBa.
* User: 辛聪明
* Date: 2021/3/11
* 根据给出的数字判断9*9数独的有效性,是否满足行、列、组内只有一个
*/
public class ValidateSuDoKu {
public boolean validate(char[][] input){
HashMap<Integer,Integer>[] rows = new HashMap[9];
HashMap<Integer,Integer>[] columns = new HashMap[9];
HashMap<Integer,Integer>[] groups = new HashMap[9];
//初始化
for(int i=0;i<rows.length;i++){
rows[i] = new HashMap<Integer, Integer>();
columns[i] = new HashMap<Integer, Integer>();
groups[i] = new HashMap<Integer, Integer>();
}
for (int i=0;i<rows.length;i++){
for (int j=0;j<rows.length;j++){
char unit = input[i][j];
if(unit != '.'){
int cell = (int) unit;
rows[i].put(cell,rows[i].getOrDefault(cell,0)+1);
columns[j].put(cell,columns[j].getOrDefault(cell,0)+1);
int groupIndex = (j/3)*3 + i/3; //格子序号
groups[groupIndex].put(cell,groups[groupIndex].getOrDefault(cell,0)+1);
if(rows[i].get(cell)>1 || columns[j].get(cell)>1 || groups[groupIndex].get(cell)>1) return false;
}
}
}
return true;
}
}
22、解数独(递归、回溯)
public class Sudoku {
private static boolean[][] row = new boolean[9][9]; //记录行已存在的数字,即row[0][1]:第一行已存在数值1
private static boolean[][] column = new boolean[9][9]; //记录列已存在的数字
private static boolean[][][] group = new boolean[3][3][9]; //记录3*3棋盘已存在的数字 即:组
private static List<int[]> cells = new ArrayList<int[]>(); //存放空白cell
private static boolean valid = false; //execute全局终止依据
private int[][] plate = new int[9][9]; //整个棋盘
public int[][] start (){
//将棋盘单元格放进list
for (int i=0; i<plate.length; i++) {
for (int j=0; j < plate.length; ++j) { //遍历每一个单元格
cells.add(new int[]{i, j});
}
}
execute(cells,0);
return plate;
}
private void execute(List<int[]> input,int index){
if(index == input.size()) {
valid = true; //跳出整个循环
return;
}
int i = input.get(index)[0], j = input.get(index)[1];
for (int num=0; num<9 && !valid; num++) { //进行赋值,范围{1~9}
//行、列、组不可重复,满足笼大小范围内
if (row[i][num] || column[j][num] || group[i / 3][j / 3][num]
|| (getLongTarget(i,j)-getLongCurrentSum(i,j)-num)<=0) continue;
row[i][num] = column[j][num] = group[i / 3][j / 3][num] = true;
plate[i][j] = num + 1; //赋值
execute(input, index+1); //递归
//迭代完都没有满足条件值
row[i][num] = column[j][num] = group[i / 3][j / 3][num] = false;
}
}
//已有方法:当前所在笼的所需的数字和
private int getLongTarget(int i,int j){
return 0;
}
//已有方法:当前所在笼已填入数字的加和
private int getLongCurrentSum(int i,int j){
return 0;
}
}
23、爬楼梯(递归、动态规划、斐波那契数列)
/**
* Created by xinBa.
* User: 辛聪明
* Date: 2021/3/14
* 递归 爬楼梯
* 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
* 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
* 输入: 2
* 输出: 2
* 解释: 有两种方法可以爬到楼顶。
* 1. 1 阶 + 1 阶
* 2. 2 阶
*/
public class Recursion {
public static void main(String[] args) {
Recursion rec = new Recursion();
// int res = rec.factorial(10);
// System.out.println(res);
int res = rec.fibonacci(10);
// 1 2 3 5 8 13 21 34 55 89
System.out.println(res);
}
/**
* 求阶乘
*/
public int factorial(int num){
if(num == 1) return 1;
return num * factorial(num-1);
}
/**
* 斐波那契数列求迭代num次返回的值
*/
public int fibonacci(int num){
// if(num <= 1) return 1;
// return fibonacci(num-1)+fibonacci(num-2);
/**
* 时间复杂度On 在复杂度情况下优化了空间复杂度O1
*/
if (num <= 3) {
return num;
}
int[] record = new int[3];
record[0] = 1;
record[1] = 2;
for (int i=2;i<num;i++){
record[i%3] = record[(i-1)%3] + record[(i-2)%3];
}
return record[(num-1)%3];
}
/*
* DFS 深度优先 递归
*/
public int climbStairsDFS(int n) {
// n == 1 -> 1
// n == 2 -> 2
// n == 0 -> 0
if (n < 3) {
return n;
}
return climbStairs(n - 1) + climbStairs(n - 2);
}
/*
* DP 广度优先 动态规划
*/
public int climbStairs(int n) {
if (n < 3) {
return n;
}
int[] steps = new int[n + 1];
steps[1] = 1;
steps[2] = 2;
for (int i = 3; i <= n; i++) {
steps[i] = steps[i - 1] + steps[i - 2];
}
return steps[n];
}
}
24、删除排序链表中的重复元素
/**
* Created by xinBa.
* User: 辛聪明
* Date: 2021/3/19
* Topic: 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
*/
public class DelListNodes {
public static void main(String[] args) {
// 1 2 2 4 4 7 8
ListNode listNode = new ListNode(1,new ListNode(2,new ListNode(2,new ListNode(4,new ListNode(4,new ListNode(7,new ListNode(8,null)))))));
DelListNodes listNodes = new DelListNodes();
System.out.println(listNode);
ListNode result = listNodes.deleteDuplicates(listNode);
System.out.println(result);
System.out.println(listNode);
System.out.println(listNode==result); //true
}
public ListNode deleteDuplicates(ListNode head) {
if(head==null || head.next==null) return head;
ListNode item = head; //这是一个指针
while (item.next != null && item.next != null){
if(item.val == item.next.val){
item.next = item.next.next ;
}else {
item = item.next; //将指针在listNode上移动到下一个位置
}
}
return head;
}
public static class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
@Override
public String toString() {
// return "ListNode{" +
// "val=" + val +
// ", next=" + next +
// '}';
return "{val=" + val +
", next=" + next +
'}';
}
}
}
25、合并两个有序数组
/**
* Created by xinBa.
* User: 辛聪明
* Date: 2021/3/20
* 给你两个有序整数数组 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]
*/
public class MergeSortArray {
// 方法一:双指针 时间复杂度O(n),空间复杂度O(n)
public int[] merge2(int[] nums1, int m, int[] nums2, int n) {
// 临时变量存储排序后的数组
int[] temp = new int[nums1.length];
// 双指针用于记录nums1,nums2两个数组下标
int p=0, q=0;
for (int i = 0; i < temp.length; i++) {
// nums1指针移动结束
if (p > m-1) {
temp[i] = nums2[q];
q++;
}
// nums2指针移动结束
else if (q>n-1) {
temp[i] = nums1[p];
p++;
}
// 用于nums1和nums2连个对比,谁小赋值给temp
else {
if (nums1[p] >= nums2[q]) {
temp[i] = nums2[q];
q++;
}
else {
temp[i] = nums1[p];
p++;
}
}
}
return temp;
}
// 方法二:先将nums2放到num1,然后快排。时间复杂度O(nlogn),空间复杂度O(logn)
public void merge(int[] nums1, int m, int[] nums2, int n) {
for (int i = 0; i < n; i++) {
nums1[m+i] = nums2[i];
}
quickSort(nums1, 0, nums1.length-1);
}
private void quickSort(int[] nums, int start, int end) {
if (nums == null || nums.length == 0) {
return;
}
int left = start;
int right = end;
int pivot = left + ((right - left) >> 1);
while (left <= right) {
while (left <= right && nums[left] < nums[pivot]) {
left ++;
}
while (left <= right && nums[right] > nums[pivot]) {
right --;
}
if (left <= right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
left++;
right--;
}
quickSort(nums, left, end);
quickSort(nums, start, right);
}
}
}
26、判断树是否相同
/**
* 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
* 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
*/
public class SameTree {
List<Integer> pList = new LinkedList<Integer>();
List<Integer> qList = new LinkedList<Integer>();
public boolean isSameTree(TreeNode p, TreeNode q) {
//方法一:遍历树放到2个数组,在对比值
loop(p,pList);
loop(q,qList);
return pList.equals(qList);
}
public void loop(TreeNode tree , List<Integer> list){
if (tree == null) return ;
list.add(tree.val);
if(tree.left != null) {
loop(tree.left,list);
}else {
list.add(null);
}
if(tree.right != null) {
loop(tree.right,list);
}else {
list.add(null);
}
}
//方法二:深度优先搜索 两棵树没向下搜索一次进行一次对比,减少不必要的遍历,时间复杂度 O(min(m,n)))
public boolean loopCompare(TreeNode p, TreeNode q){
//同时为空的时候符合规范返回true,因为最后是所有节点对比取 &&,
if(p==null & q==null) return true;
if (p == null || q == null) return false;
if (p.val != q.val) return false;
return loopCompare(p.left,q.left) && loopCompare(p.right,q.right); //重点
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
}
27、对称二叉树
/**
* 给定一个二叉树,检查它是否是镜像对称的。
*/
public class isSymmetric {
public boolean isSymmetric(TreeNode root) {
if (root == null) return false;
return compare(root.left,root.right);
}
public boolean compare(TreeNode left , TreeNode right){
if(left == null && right == null) return true;
if(left == null || right == null || left.val != right.val) return false;
return compare(left.left,right.right) && compare(left.right,right.left); //左跟右比较
}
28、求树的深度
public int maxDepth2(TreeNode root) {//深度优先
if (root == null) {
return 0;
} else {
int leftHeight = maxDepth(root.left);
int rightHeight = maxDepth(root.right);
return Math.max(leftHeight, rightHeight) + 1;
}
}
29、将有序数组转换为二叉搜索树
/**
* 给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
* 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
* 数组已排序,中位数可以直接提根节点,然后分段提中位数由上到下完成BST树构建,递归
* 容易遗漏点:递归终止条件, node.left 来连接而不是直接传node下去
*/
public class SortedArrayToBST {
public TreeNode sortedArrayToBST(int[] nums) {
int left = 0;
int right = nums.length-1;
return convert(nums,left,right);
}
private TreeNode convert(int[] array,int left,int right){
if(left>right) return null;
int mid = left + ((right-left) >> 1);
TreeNode node = new TreeNode(array[mid]);
node.left = convert(array,left,mid-1);
node.right = convert(array,mid+1,right);
return node;
}
}
30、平衡二叉树
/**
* 给定一个二叉树,判断它是否是高度平衡的二叉树。
* 本题中**,一棵高度平衡二叉树定义为:
* 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1
*/
public class BalanceTree {
/**
* 方法一:自顶向下,前序遍历,嵌套递归,每一个节点都要求出来层数,时间复杂度O(n2) n是节点个数
*/
public boolean isBalanced(TreeNode root) {
if (root==null) return true;
return Math.abs(height(root.left)-height(root.right)) <= 1
&& isBalanced(root.left) && isBalanced(root.right); //递归每一个节点
}
//计算当前节点的深度
private int height(TreeNode node){//递归求出层数
if(node==null) return 0;
return Math.max(height(node.left),height(node.right))+1; // +1 代表层数
}
/**
* 方法二:自低向下,后序遍历,当有不满足情况返回-1,一直传递到根节点,时间复杂度O(n) n是节点个数
*/
public boolean isBalanced2(TreeNode root) {
return height2(root) >= 0;
}
//计算当前节点的深度,如果有不满足高度平衡二叉树的情况就返回-1
private int height2(TreeNode node){//递归求出层数
if(node==null) return 0;
int lHeight = height2(node.left);
int rHeight = height2(node.right);
if(lHeight == -1 || rHeight==-1 || Math.abs(lHeight-rHeight)>1){//不满足高度平衡二叉树
return -1;
}
return Math.max(lHeight,rHeight)+1; // +1 代表层数
}
}
31、二叉树最小深度
/**
* 给定一个二叉树,找出其最小深度。
* 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
*/
public class TreeMinDepth {
//深度优先算法一
public int minDepth(TreeNode root) {
if(root == null) return 0;
return dfs(root,0);
}
private int dfs(TreeNode node,int depth){//时间复杂度On
if (node==null) return Integer.MAX_VALUE; //只有叶子节点到根节点才算深度
if (node.left == null && node.right==null) return depth+1;
int left = dfs(node.left,depth+1);
int right = dfs(node.right,depth+1);
return Math.min(left,right);
}
//深度优先算法二
private int minDepth2(TreeNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
int min_depth = Integer.MAX_VALUE;
if (root.left != null) {
min_depth = Math.min(minDepth2(root.left), min_depth);
}
if (root.right != null) {
min_depth = Math.min(minDepth2(root.right), min_depth);
}
return min_depth + 1;
}
}
32、树路径总和
/**
* 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,
* 这条路径上所有节点值相加等于目标和 targetSum 。
*/
public class TreeHasPathSum {
/**
* 深度优先 时间复杂度On
*/
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root == null) return false; //有节点值负数情况
targetSum -= root.val;
if(targetSum==0 && root.left==null && root.right==null) return true;//刚好=sum && 叶子节点
return hasPathSum(root.left,targetSum) || hasPathSum(root.right,targetSum);
}
}
33、反转链表
/**
* 反转单链表
* 输入: 1->2->3->4->5->NULL
* 输出: 5->4->3->2->1->NULL
*/
public class ReverseList {
/**
* 方法一:利用栈性质 时间复杂度On 空间复杂度On
*/
public ListNode reverseList(ListNode head) {
if (head==null || head.next==null) return head;
Stack<ListNode> stack = new Stack<>(); //栈 先进后出
ListNode node = head;
ListNode temp ;
for (;;){
if (node.next==null) break;
stack.push(node);
node = node.next;
}
temp = node;
while (!stack.isEmpty()){
temp.next = stack.pop();
temp=temp.next;
}
temp.next = null;
return node;
}
/**
* 方法二:迭代 时间复杂度:On 空间复杂度O1
*/
public ListNode reverseList2(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while (curr != null) {
ListNode next = curr.next; //临时节点
curr.next = prev; //将指针反转
prev = curr; //将前一个指针后移
curr = next; //将当前指针后移
}
return prev;
}
}
反转单词
String str = "you are how";
String[] split = str.split(" ");
StringBuilder result = new StringBuilder();
for(int i=split.length-1;i<split.length;i--){
result.append(split[i]+" ");
}
result.toString().trim();
反转指定两个下标位置
/**
* 百度算法面试题
*/
public class ReverseIndexList {
/**
* 反转链表
* 给定头指针head和left right下标(left<right) ,将两个位置反转
* 输入: [12345] left:2 right:4
* 輸出: [14325]
*/
public ListNode sweap(ListNode head,int left,int right){
ListNode node = head; //临时指针
ListNode leftNode = null; //存储left前一个位置指针
for (int i=1;i<=right;i++){
if(i==left-1) {
leftNode = node;
}else if(i==right-1){
ListNode tempLeft = leftNode.next; //左节点
ListNode tempRight = node.next; //右节点
leftNode.next = tempRight; //改变指针的指向,思路画个图就清晰
node.next = tempLeft;
node.next.next = tempRight.next;
tempRight.next = node;
return head;
}
node = node.next;
}
return null;
}
}
34、杨辉三角
/**
* 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
* 在杨辉三角中,每个数是它左上方和右上方的数的和。
* 输入: 5
* 输出:
* [
* [1],
* [1,1],
* [1,2,1],
* [1,3,3,1],
* [1,4,6,4,1]
* ]
*/
public class YangTriangle {
/**
* 时间复杂度:O(numRows2)。
* 空间复杂度:O(1)。不考虑返回值的空间占用。
*/
public List<List<Integer>> generate(int numRows) {
List<List<Integer>> list = new LinkedList<>();
if(numRows<1) return list;
for (int i=0;i<numRows;i++){
LinkedList<Integer> rowList = new LinkedList<Integer>();
for (int j=0;j<=i;j++){
if (j==0 || j == i) {
rowList.add(1);
} else {
rowList.add(list.get(i-1).get(j-1) + list.get(i-1).get(j));
}
}
list.add(rowList);
}
return list;
}
}
35、杨辉三角2(求指定行)
/**
* 给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。
* 在杨辉三角中,每个数是它左上方和右上方的数的和。
* 输入: 3
* 输出: [1,3,3,1]
*/
public class YangTriangleGetRow {
//增加一个公共变量存储第几行的数据,下次获取就不需要在递归到第0层
Map<Integer,List<Integer>> triangle = new HashMap<Integer,List<Integer>>();
public List<Integer> getRow(int rowIndex) {
List<Integer> list = new LinkedList<>();
if(triangle.containsKey(rowIndex)) return triangle.get(rowIndex);
if(rowIndex == 0) {
list.add(1);
}else {
for (int i=0;i<=rowIndex;i++){
if(i==0 || i==rowIndex) {
list.add(1);//第一位和最后一位是1,否则数组越界
}else {
List<Integer> rowFont = getRow(rowIndex-1);
triangle.put(rowIndex-1,rowFont);
list.add(rowFont.get(i-1)+rowFont.get(i));
}
}
}
return list;
}
}
36、买股票的最佳时机
/**
* 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
* 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
* 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
* 输入:[7,1,5,3,6,4]
* 输出:5
* 输入:prices = [7,6,4,3,1]
* 输出:0
*/
public class MaxProfit {
/**
* 思路:最低点买入,然后遍历在之后的最高点是最大利润
* 最低点是动态的,最大利润也是动态的,遍历一次求出最终的
*/
public static int maxProfit(int[] prices){
int minPrice = Integer.MAX_VALUE;
int maxProfile = 0;
for(int i=0;i<prices.length;i++){
if(prices[i]<minPrice){
minPrice = prices[i];
}
if(maxProfile<(prices[i]-minPrice)){
maxProfile = prices[i]-minPrice;
}
}
return maxProfile;
}
}
37、买股票的最佳时机二(多次买入卖出)
/**
* 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
* 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
* 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
* 输入: [7,1,5,3,6,4]
* 输出: 7
* 解释: 第二天买,第三天卖,第四天买,第五天卖
*/
public class MaxProfitPlus {
/**
* 贪心算法:整体最优解等于不相关的子区间最优解 时间复杂度:O(n) 时间复杂度:O(1)
*/
public int maxProfit(int[] prices){
int maxVal = 0;
for (int i=1;i<prices.length;i++){
maxVal += Math.max(0,prices[i]-prices[i-1]);
}
return maxVal;
}
/**
* 动态规划:多阶段解决,解决子区间重叠问题 时间复杂度:O(n) 时间复杂度:O(n)
*/
public int maxProfitDP(int[] prices){
int[][] record = new int[prices.length][2];
record[0][0] = 0; //当天没有买股票的最高收入
record[0][1] = -prices[0]; //当天买入股票的最高收入
for (int i=1;i<prices.length;i++){
record[i][0] = Math.max(record[i-1][0],record[i-1][1]+prices[i]); //昨天[0]和今天[0]的最大
record[i][1] = Math.max(record[i-1][1],record[i][0]-prices[i]); //昨天[1]和今天[1]的最大
}
return record[prices.length-1][0];
}
/**
* 动态规划 + 滚动数组优化 时间复杂度:O(n) 时间复杂度:O(1)
*/
public int maxProfitDPScroll(int[] prices){
int[][] record = new int[2][2];
record[0][0] = 0; //当天没有买股票的最高收入
record[0][1] = -prices[0]; //当天买入股票的最高收入
for (int i=1;i<prices.length;i++){
record[i%2][0] = Math.max(record[(i-1)%2][0],record[(i-1)%2][1]+prices[i]); //昨天[0]和今天[0]的最大
record[i%2][1] = Math.max(record[(i-1)%2][1],record[i%2][0]-prices[i]); //昨天[1]和今天[1]的最大
}
return record[(prices.length-1)%2][0];
}
}
38、验证回文串
/**
* 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
* 说明:本题中,我们将空字符串定义为有效的回文串。
* 示例 1:
* 输入: "A man, a plan, a canal: Panama"
* 输出: true
*/
public class IsPalindrome {
/**
* 双指针 时间复杂度O(n)
*/
public boolean isPalindrome(String s) {
if(s==null || s.equals("")) return true;
char[] chars = s.toCharArray();
int len = chars.length;
int rIndex = chars.length-1;
for (int i=0;i<len;i++){
char left = chars[i];
if (!Character.isLetterOrDigit(left)) continue; //移动前指针
while (!Character.isLetterOrDigit(chars[rIndex])) {
--rIndex; //移动后指针
}
if (i>rIndex) return true;
char right = chars[rIndex];
if(left != right){
if(!Character.isLetter(left) || !Character.isLetter(right) ||
Math.abs(left-right)!=32) return false; //如果不是字母且忽略大小写不等,返回false
}
--rIndex; //移动后指针
}
return true;
}
/**
* 方法二:筛选+判断 时间复杂度O(n)
*/
public boolean isPalindrome2(String s){
StringBuilder just = new StringBuilder();
for (int i=0;i<s.length();i++){
char at = s.charAt(i);
if(Character.isLetterOrDigit(at)) just.append(Character.toLowerCase(at)); //数字也可以转大小写,不变
}
StringBuilder reverse = new StringBuilder(just).reverse(); //不new的话把just也反转了
return just.toString().equals(reverse.toString());
}
}
39、只出现一次的数字
/**
* 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
* 说明:
* 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
* 示例 1:
* 输入: [2,2,1]
* 输出: 1
*/
public class SingleNumber {
/**
* 异或运算符(^)
* 参加运算的两个数,按二进制位进行“异或”运算。
* 运算规则:参加运算的两个数,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。
* 即 0 ^ 0=0 , 0 ^ 1= 1 , 1 ^ 0= 1 , 1 ^ 1= 0 。
* 例: 2 ^ 4 即 00000010 ^ 00000100 =00000110 ,所以 2 ^ 4 的值为6 。
*
* 总结:1、任何数和 0 做异或运算,结果仍然是原来的数,即 a^0=a。
* 2、任何数和其自身做异或运算,结果是 0,即 a^a=0。
* 3、异或运算满足交换律和结合律,即 a^b^a=b^a^a=b^(a^a)=b^0==b。
* 即:十进制下 相同数字 ^ 会抵消
*
* 时间复杂度:O(n),其中 n 是数组长度。只需要对数组遍历一次。
* 空间复杂度:O(1)
*/
public int singleNumber(int[] nums) {
int single = 0;
for (int num : nums) {
single ^= num;
}
return single;
}
}
40、两数相加
/**
* 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
*
* 请你将两个数相加,并以相同形式返回一个表示和的链表。
* 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
*/
public class SumAdd {
public static void main(String args[]) {
ListNode l1 = new ListNode(2,new ListNode(4,new ListNode(3)));
ListNode l2 = new ListNode(5,new ListNode(6,new ListNode(4)));
/**
* 342 + 465 = 807
*/
SumAdd sumAdd = new SumAdd();
System.out.println(sumAdd.addTwoNumbers(l1,l2));
}
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = null, tail = null; //head是结果的头节点 用于返回
int carry = 0;
while (l1 != null || l2 != null) { //都是null的时候才结束
int n1 = l1 != null ? l1.val : 0;
int n2 = l2 != null ? l2.val : 0;
int sum = n1 + n2 + carry;
if (head == null) { //第一次
head = tail = new ListNode(sum % 10);
} else {
tail.next = new ListNode(sum % 10);
tail = tail.next;
}
carry = sum / 10; //进位数值
if (l1 != null) {
l1 = l1.next;
}
if (l2 != null) {
l2 = l2.next;
}
}
if (carry > 0) {
tail.next = new ListNode(carry);
}
return head;
}
public static class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
@Override
public String toString() {
return "ListNode{" +
"val=" + val +
", next=" + next +
'}';
}
}
}
41、环形链表(双指针、快慢指针)
/**
* 给定一个链表,判断链表中是否有环。
* 环:链表next指向之前的节点,形成死循环
*/
public class HasCycle {
/**
* 时间复杂度:O(N) 空间复杂度:O(N)
*/
public boolean hasCycle(ListNode head) {
if(head==null || head.next == null) return false;
Set<ListNode> set = new HashSet<ListNode>();
ListNode node = head;
while (node.next != null){
if(!set.add(node)) return true; //如果此 set 已包含该元素,则该调用不更改 set 并返回 false。
node = node.next;
}
return false;
}
/**
* 时间复杂度:O(N) 空间复杂度:O(1)
* 快慢指针 如果是环快慢指针肯定会相遇
*/
public boolean hasCycle2(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
42、最小栈
/**
* 辅助栈
* 时间复杂度均为 O(1)。因为栈的插入、删除与读取操作都是 O(1),我们定义的每个操作最多调用栈操作两次。
* 空间复杂度:O(n),其中 n 为总操作数。
*/
class MinStack {
//存放实际数据
Deque<Integer> stack = null;
//辅助栈 用于存放最小数字
Deque<Integer> minStack = null;
public MinStack() {
stack = new LinkedList<>();
minStack = new LinkedList<>();
minStack.push(Integer.MAX_VALUE);
}
public void push(int val) {
stack.push(val);
minStack.push(Math.min(minStack.peek(),val)); //辅助栈的栈顶永远是最小的
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}
43、相交链表
/**
* 相交链表:找到两个单链表相交的起始节点。
* 前提条件:值相等的node是单例,只要node相等就是相交,无视后续节点
* 输入:listA = [4,1,8,4,5], listB = [5,0,1,8,4,5]
* 返回 [8,4,5]
*/
public class IntersectLinked {
public static void main(String[] args) {
ListNode headA = new ListNode(3);
ListNode node1 = new ListNode(4);
ListNode node2 = new ListNode(5);
ListNode node3 = new ListNode(6);
ListNode node4 = new ListNode(7);
headA.next = node1;
headA.next.next = node2;
ListNode headB = new ListNode(2);
headB.next = node4;
headB.next.next = node3; // next指向同一个地址
IntersectLinked linked = new IntersectLinked();
System.out.println(linked.getIntersectionNode3(headA,headB));
}
/**
* 暴力法:遍历,时间复杂度O(m*n) mn是两个链表的长度 空间复杂度O(1)
*/
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA==null || headB==null) return null;
ListNode pointA = headA;
ListNode pointB = headB;
while(pointA != null){
while (pointB != null){
if (pointA==pointB) return pointA;
pointB = pointB.next;
}
pointA = pointA.next;
pointB = headB;
}
return null;
}
/*
* 哈希表法:遍历A每一个节点地址存到哈希表,遍历B,如果相等则是B此节点
* 时间复杂度 : O(m+n)。空间复杂度 : O(m) 或 O(n)。
*/
public ListNode getIntersectionNode2(ListNode headA, ListNode headB) {
if(headA==null || headB==null) return null;
Set<ListNode> set = new HashSet<>();
while(headA!=null){
set.add(headA);
headA = headA.next;
}
while (headB!=null){
if (set.contains(headB)) return headB;
headB = headB.next;
}
return null;
}
/**
* 双指针:一个指针遍历A,一个遍历B。当A或B遍历完了,就调换遍历,如果相交,总有相遇的时候
* 时间复杂度:O(mn) 空间复杂度O(1)
*/
public ListNode getIntersectionNode3(ListNode headA, ListNode headB) {
// 特判
if (headA == null || headB == null) return null;
ListNode head1 = headA;
ListNode head2 = headB;
while (head1 != head2) { //如果不相交 head1\head2同时为null时结束
if (head1 != null) {
head1 = head1.next;
} else {
head1 = headB;
}
if (head2 != null) {
head2 = head2.next;
} else {
head2 = headA;
}
}
return head1;
}
}
44、合并排序(归并排序)
/**
* 合并排序(归并排序) 核心:分治
* 先将无序序列利用二分法划分为子序列,直至每个子序列只有一个元素(单元素序列必有序),
* 然后再对有序子序列逐步(两两)进行合并排序
*/
public class MergeSorted {
public static void mergeSort(int[] array){
int length=array.length;
int middle=length/2;
if(length>1){
int[]left= Arrays.copyOfRange(array,0,middle);//拷贝数组array的左半部分 [0,mid)
int[]right=Arrays.copyOfRange(array,middle,length);//拷贝数组array的右半部分 [mid.len)
mergeSort(left);//递归array的左半部分
mergeSort(right);//递归array的右半部分
merge(array,left,right);//数组左半部分、右半部分合并到Array
}
}
//合并数组,升序
private static void merge(int[]result,int[]left,int[]right){
int i=0,l=0,r=0;
while(l<left.length&&r<right.length){
if(left[l]<right[r]){
result[i]=left[l];
i++;
l++;
}else{
result[i]=right[r];
i++;
r++;
}
}
while(r<right.length){//如果右边剩下合并右边的
result[i]=right[r];
r++;
i++;
}
while(l<left.length){
result[i]=left[l];
l++;
i++;
}
}
}
45、Excel表列名称
/**
* 给定一个正整数,返回它在 Excel 表中相对应的列名称。
* 1 -> A
* 2 -> B
* 3 -> C
* ...
* 26 -> Z
* 27 -> AA
* 28 -> AB
* ...
* 输入: 1
* 输出: "A"
* -----------
* 输入: 28
* 输出: "AB"
* -----------
* 输入: 701
* 输出: "ZY"
*/
public class StaticProxy {
public String convertToTitle(int columnNumber) {
if(columnNumber<1) return "";
StringBuilder res = new StringBuilder();
while (columnNumber>0){ //int 除数小于1的时候转0
columnNumber--; //因为模余是以0开始,所以先减1
res.append((char)('A'+columnNumber % 26));
columnNumber /= 26;
}
return res.reverse().toString();
}
}
46、Excel表列序号
/**
* 给定一个Excel表格中的列名称,返回其相应的列序号。
* A -> 1
* B -> 2
* C -> 3
* ...
* Z -> 26
* AA -> 27
* AB -> 28
* ...
* 输入: "A"
* 输出: 1
* -------------
* 输入: "ZY"
* 输出: 701
*/
public class ExcelTitleToNum {
/**
* 对高位每个位依次转为十进制数 再相加
* 时间复杂度:O(n2)
*/
public int titleToNumber(String columnTitle) {
char[] title = columnTitle.trim().toCharArray();
int num =0;
int len = title.length;
for (int i=0;i<len;i++){
char ch = title[i];
num += Math.pow(26,(len-1-i))*((ch-'A')+1); //公式:26的 (位数-1) 次方 * 当前数值
}
return num;
}
/**
* 对高位每个位与下一位 转为十进制数 最后转换完毕
* 时间复杂度:O(n)
*/
public int titleToNumber2(String s) {
int ans = 0;
for(int i=0;i<s.length();i++) {
int num = s.charAt(i) - 'A' + 1;
ans = ans * 26 + num;
}
return ans;
}
}
47、多数元素
/**
* 给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
* 进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
*/
public class MajorityElement {
/**
* 方法一:哈希
* 时间复杂度:O(n) 空间O(n)
*/
public int majorityElement(int[] nums) {
int len = nums.length;
int stand = len >> 1;
HashMap<Integer, Integer> map = new HashMap<>();
for (int i=0;i<len;i++){
int key = nums[i];
map.put(key,map.getOrDefault(key,0)+1); //不存在key返回 0
if (map.get(key)>stand) return key;
}
return -1;
}
/**
* 算法2:排序
* arrays.sort() 采用分策略算法 len<47 -> insert sort len<286 ->quick sort len>286 merge sort
* 时间复杂度:O(nlogn)。
* 空间复杂度:O(logn)。 自己写排序算法可以达到O(1)
*/
public int majorityElement2(int[] nums) {
Arrays.sort(nums);
return nums[nums.length >> 1]; // 多数元素>(n/2) so 排序后 n/2 的位置比是多数元素
}
/**
* 算法3:随机化
* 因为超过 n/2的数组下标被众数占据了,这样我们随机挑选一个下标对应的元素并验证,有很大的概率能找到众数。
* 最坏时间复杂度:O(无限)
*/
/**
* 算法4:摩尔投票法思路
* 一次遍历,以当前元素做为候选人,遇到等值元素count+1,否则-1,当count==0时,当前元素替换候选人
* 显然最后和大于 0,从结果本身我们可以看出众数比其他数多。
* 时间复杂度:O(n) 空间复杂度:O(1)
*/
public int moerSulotion(int[] nums){
int count = 1; //计数器
int curVal = nums[0]; //候选人
for (int i=1;i<nums.length;i++){
if(count==0) curVal = nums[i];
if(curVal == nums[i]) {
count++;
}else{
count--;
}
}
return curVal;
}
}
48、字符串相加(大数相加)
/**
* 大数相加
* 给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。
* 提示:
* 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式
*/
public class BigNumAdd {
/**
* 算法:模拟 [竖式加法]
* 时间复杂度:O(max(len1,len2)) 空间复杂度O(1)
*/
public String addStrings(String num1, String num2) {
StringBuilder result = new StringBuilder();
char[] arr1 = num1.toCharArray();
char[] arr2 = num2.toCharArray();
int len1 = arr1.length;
int len2 = arr2.length;
int add = 0;
for (int i=1;;i++){ //倒序遍历 最后一位len-1
if (i > len1 && i> len2) break; //两个数组都遍历结束
int num = 0; //进位
if(i> len1) num = (arr2[len2-i]-'0') + add ; //arr1遍历结束
if(i> len2) num = (arr1[len1-i]-'0') + add ;
if(i<=len1 && i<=len2) num = (arr1[len1-i]-'0') + (arr2[len2-i]-'0') + add ;
add = num/10; //int 小数转整型强制转换
result.append(num%10); //取余
}
if (add != 0) result.append(add); //存在情况 最后进1 而循环结束
return result.reverse().toString();
}
}
49、阶乘后的零
/**
* 阶乘后0的个数
* 给定一个整数 n,返回 n! 结果尾数中零的数量
* 输入: 3 3! = 6
* 输出: 0
*--------------------------
* 输入: 5 5! = 120
* 输出: 1
*/
public class TrailingZeroes {
/**
* 算法一:时间复杂度:O(N)
*/
public static int trailingZeroes(int n) {
if(n==0) return 0;
BigInteger result = BigInteger.ONE;
while(n>1){
result = result.multiply(BigInteger.valueOf(n--));
}
int zeroCount=0;
while (result.mod(BigInteger.TEN).equals(BigInteger.ZERO)) {
result = result.divide(BigInteger.TEN);
zeroCount++;
}
return zeroCount;
}
/**
* 算法二:计算因子 5
* 因为计算0的个数,即是10的几次方
* 阶乘是乘法,2*5=10,1*10=1*2*5=10
* 所以计算0的个数即是算有多少个5的因子
* 时间复杂度:O(n) 空间 O(1)
*/
public static int executeZeroNum(int n){
int zeroNum = 0;
for (int i=5;i<=n;i+=5){ //只有5的公倍数才有因子5
int val = i;
while(val%5==0){
zeroNum ++;
val /= 5; //像25这样有两个因子5
}
}
return zeroNum;
}
/**
* 算法三:高效的因子5个数
* 我们不必每次尝试 5 的幂,而是每次将 n 本身除以 5,两种操作等价
* 时间复杂度:O(logn) 空间复杂度:O(1)
*/
public int calculateZeroNum(int n) {
int zeroCount = 0;
long currentMultiple = 5;
while (n >= currentMultiple) {
zeroCount += (n / currentMultiple);
currentMultiple *= 5;
}
return zeroCount;
}
}
50、组合两个表
##编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:
表1: Person
+-------------+---------+
| 列名 | 类型 |
+-------------+---------+
| PersonId | int |
| FirstName | varchar |
| LastName | varchar |
+-------------+---------+
PersonId 是上表主键
表2: Address
+-------------+---------+
| 列名 | 类型 |
+-------------+---------+
| AddressId | int |
| PersonId | int |
| City | varchar |
| State | varchar |
+-------------+---------+
AddressId 是上表主键
##左外连接 不是每个人都有address信息
select tb1.FirstName,tb1.LastName,tb2.City,tb2.State
from
Person as tb1
left join
Address tb2
on
tb1.PersonId = tb2.PersonId;
51、第二高薪水
# 编写一个 SQL 查询,获取 Employee 表中第二高的薪水Salary,没有第二返回null
# 方法一:limit
# 重点:distinct去重,如果没有这样的第二最高工资,这个解决方案将被判断为 “错误答案”,
# 因为本表可能只有一项记录。为了克服这个问题,我们可以将其作为临时表。
select (
select distinct Salary
from Employee
order by Salary desc
limit 1 offset 1
) SecondHighestSalary;
# 方法二:IFNULL
select (
ifnull(
(select distinct Salary
from Employee
order by Salary desc
limit 1 offset 1),
null
)
) SecondHighestSalary;
52、超过经理收入的员工
#给定 Employee 表,编写一个 SQL 查询,该查询可以获取收入超过他们经理的员工的姓名
#内连接 返回两个表条件符合的数据行
select a.Name as Employee
from
Employee a, Employee b
where a.ManagerId = b.Id
and a.Salary > b.Salary;
#join 内连接 更加有效
select a.Name as Employee
from
Employee a join
Employee b
on a.ManagerId = b.Id
and a.Salary > b.Salary;
53、查找重复的电子邮箱
#编写一个 SQL 查询,查找 Person 表中所有重复的电子邮箱。
# group having
select Email from Person
group by Email
having count(Email)>1;
# 左外连接
select distinct a.Email from
Person a left join Person b
on a.Email = b.Email
and a.Id != b.Id
where b.id is not null;
#group where
select Email from
(select Email,count(Email) num from Person
group by Email) tb
where tb.num > 1;
54、从不订购的客户
# not in
select a.Name as Customers from Customers a
where a.Id not in (
select distinct CustomerId from Orders
);
# left join 性能较好
select a.Name as Customers from Customers a
left join Orders b
on a.Id = b.CustomerId
where b.CustomerId is null;
55、TOPN 问题、第几大问题
/**
* 给定一个list 找到其中第k大的元素
* 面试官大多数不想要这个答案,可以用快排
*/
public Integer findTop (Integer[] num, int k){
// PriorityQueue 优先级队列 ,升序:小顶堆 降序:大顶堆
// num.length 是队列长度,超出长度则把末端的挤掉,用于求top n ,则长度直接设置 n
PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(num.length, (o1, o2) -> {
return o2 - o1;
});
for (int i=0; i<num.length; i++){
priorityQueue.add(num[i]);
}
for (int i=0; i<k-1; i++){
//弹出、丢掉
priorityQueue.poll();
}
//取值,不弹出
return priorityQueue.peek();
}
56、LRU算法
/**
* @author xinjianan
* @Description Lru 最近最少使用,常用的淘汰算法
* @createTime 2021年07月22日 17:26:00
* @Description: 实现思路:双向链表 + hashmap 链表用于存储数据,hashmap用于存储指定key的节点,查找O(1)时间复杂度
*/
public class LRUCache {
// 头节点放冷数据,即最少使用的,尾节点放最常使用的
// 节点
class CacheNode{
int key;
int val;
CacheNode prev;
CacheNode next;
public CacheNode(){}
public CacheNode(int key, int val){
this.key = key;
this.val = val;
}
}
// 容量
private int capacity;
//用于查找
private Map<Integer, CacheNode> map = new HashMap<>();
//哨兵节点 指向头尾
private CacheNode head, tail;
//初始化 也可以给一个默认的 capacity
public LRUCache(int capacity){
this.capacity = capacity;
// 头尾相连,0容量链表(除哨兵)
this.head.next = tail;
this.tail.prev = head;
}
public void put(int key, int val){
if(map.containsKey(key)){
map.get(key).val = val;
return ;
}
//先判断是否达到最大长度,达到则删除
if(map.size() == capacity){
head.next = head.next.next;
head.next.prev = head;
}
CacheNode node = new CacheNode(key,val);
//把数据放在尾部
moveToTail(node);
//同步在hashmap
map.put(key,node);
}
public int get(int key){
if(!map.containsKey(key)){
return -1;
}
CacheNode node = map.get(key);
//将此节点脱离出来
node.prev.next = node.next;
node.next.prev = node.prev;
moveToTail(node);
return node.val;
}
//把数据节点放在尾部
private void moveToTail(CacheNode node){
node.prev = this.tail.prev;
this.tail.prev.next = node;
node.next = tail;
tail.prev = node;
}
57、链表找环
//方法一
MyNode findRedNode(MyNode head) {
MyNode point = head;
Set<MyNode> set = new HashSet<>();
while(point.next != null) {
if(set.contains(point)) {
break;
}
set.add(point);
point = point.next;
}
return point;
}
}
/**
* 方法二:快慢指针,快指针与慢指针是否相遇作为判断是否闭环的条件
*/
public MyNode isLoop(){
MyNode fast = first , slow = first;//定义两个指针,分别是快和慢
//遍历一遍链表,如果最后是null的话就代表没有环
while (fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
//如果俩相遇了,代表有环
if (fast == slow){
return fast;
}
}
return null;
}
58、二叉树分层遍历
/**
* 1
* 2 3
* 4 5 6 7
* print: 1234567
*/
public List<Integer> levelOrder(TreeNode root){
List<Integer> result = new ArrayList<>();
if(root == null){
return result;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()){
TreeNode head = queue.remove();
result.add(head.val);
if(head.left != null){
queue.add(head.left);
}
if(head.right != null){
queue.add(head.right);
}
}
return result;
}
59、字符串去重
//字符串去重,保证字母顺序,比如:zabcdefabcz -> zabcdef。
public static String distinct(String str){
StringBuilder sb = new StringBuilder();
for(int i=0; i< str.length(); i++){
if(sb.toString().indexOf(str.charAt(i))>-1){
continue;
}
sb.append(str.charAt(i));
}
return sb.toString();
}
60、快排
private void quickSort(int[] num, int start, int end) {
if (num == null || num.length == 0 || start >= end) {
return;
}
int left = start;
int right = end;
int pivot = num[start];
while (left <= right) {
while (left <= right && num[left] < pivot) {
left ++;
}
while (left <= right && num[right] > pivot) {
right --;
}
if (left <= right) {
int temp = num[left];
num[left] = num[right];
num[right] = temp;
left++;
right--;
}
}
quickSort(num, left, end);
quickSort(num,start, right);
}
61、十进制转换二进制
//方法一:Integer自带操作 Integer.toBinaryString(255)
//竖式法,记录每一次的余数
public String toBinary(Long num){
StringBuilder sb = new StringBuilder();
while (!"0".equals(num.toString())){
sb.append(num % 2);
num /= 2;
}
return sb.reverse().toString();
}
//位移法
public static void binaryToDecimal(int n){
for(int i = 31;i >= 0; i--){
System.out.print(n >>> i & 1);
}
}
62、两个队列实现一个栈
//1、 一个队列加入元素,弹出元素时,需要把队列中的 元素放到另外一个队列中,删除最后一个元素
//2、 两个队列始终保持只有一个队列是有数据的
public class StackByQueue<T> {
private Queue<T> queue1 = new LinkedList<>();
private Queue<T> queue2 = new LinkedList<>();
/**
* 入栈
*/
public boolean push(T t){
if (queue1.isEmpty()){
return queue2.offer(t);
}else {
return queue1.offer(t);
}
}
/**
* 出栈
*/
public T pop(){
if(queue1.isEmpty() && queue2.isEmpty()){
throw new RuntimeException("queue is empty!");
}
if(queue1.isEmpty() && !queue2.isEmpty()){
//取出size-1数据放入另一个队列,把最后一个元素删除返回
while (queue2.size() > 1){
queue1.offer(queue2.poll());
}
return queue2.poll();
}
if(!queue1.isEmpty() && queue2.isEmpty()){
while (queue1.size() > 1){
queue2.offer(queue1.poll());
}
return queue1.poll();
}
return null;
}
}
63、两个栈实现一个队列
/**
* 栈:先进后出
* 队列:先进先出
*
* in 专门用于存放队列增加的元素,是队列的倒序, 即 offer 123, 在in里存放是 321
* out 专门用于出栈使用,当out为空时,将in数据转换到out,即 in(321) => out(123),所以队列出列就是out的pop
*
* 时间复杂度:appendTail为 O(1),deleteHead为均摊 O(1)。对于每个元素,至多入栈和出栈各两次,故均摊复杂度为 O(1)。
* 空间复杂度:O(n)。其中 n 是操作总数。对于有 n 次 appendTail 操作的情况,队列中会有 n 个元素,故空间复杂度为 O(n)。
*
*/
public class CQueue {
private final Stack<Integer> in;
private final Stack<Integer> out;
public CQueue() {
in = new Stack<>();
out = new Stack<>();
}
public void appendTail(int value) {
in.push(value);
}
public int deleteHead() {
if (in.isEmpty() && out.isEmpty()) {
return -1;
}
if (out.isEmpty()) {
while (!in.isEmpty()) {
out.push(in.pop());
}
}
return out.pop();
}
}
64、顺序/逆序遍历矩阵
/**
* 从外围逆时针遍历矩阵-默认非空
* 3 6 0 7 8
* 3 5 9 1 0
* 0 6 7 8 9
* 1 2 3 4 5
* @param matrix 矩阵
*/
public static void forEach(int[][] num){
//行
int n = num.length;
//列
int m = num[0].length;
//临界值
int left = 0, right = m-1, up = 0, down = n-1;
//存放结果的数组、对应坐标
int[] result = new int[m*n];
int resIndex = 0;
while(left <= right && up<= down){
// 左上 -> 左下
for (int i=up; i<= down; i++){
result[resIndex] = num[i][left];
resIndex ++;
}
// 左下 -> 右下
for (int i=left+1; i<= right; i++){
result[resIndex] = num[down][i];
resIndex ++;
}
// 存在-1 情况 所以等号情况不成立
if(up<down && left < right) {
// 右下 -> 右上
for (int i = down - 1; i >= up; i--) {
result[resIndex] = num[i][right];
resIndex++;
}
// 右上 -> 左上
for (int i=right-1; i> left; i--){
result[resIndex] = num[up][i];
resIndex ++;
}
}
//边界值迭代
left ++; right--; up++; down--;
}
// 7,打印最终结果
for(int i = 0 ;i <m*n;i++){
System.out.print(result[i]+" ");
}
}
/**
* 顺时针打印矩阵
*
* 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
* 输出:[1,2,3,6,9,8,7,4,5]
*
* 时间复杂度O(mn),空间复杂度O(1)
*/
public static int[] spiralOrder(int[][] matrix) {
if (matrix == null || matrix.length == 0) {
return new int[0];
}
int row = matrix.length;
int column = matrix[0].length;
int[] temp = new int[row*column];
int up=0, down=row-1, left=0, right=column-1;
int index=0;
while (left<=right && up<= down) {
// 左上 >> 右上
for (int i = left; i <= right; i++) {
temp[index] = matrix[up][i];
index ++;
}
// 右上 >> 右下
for (int i = up+1; i <= down; i++) {
temp[index] = matrix[i][right];
index ++;
}
// 左右,上下仅有一次等于情况,两次等于就出现重复值了
if (up < down && left < right) {
// 右下 >> 左下
for (int i = right-1; i >= left; i--) {
temp[index] = matrix[down][i];
index ++;
}
// 左下 >> 左上
for (int i = down-1; i > up; i--) {
temp[index] = matrix[i][left];
index ++;
}
}
left ++; right--; up++; down--;
}
return temp;
}
65、折线图最小线段数
/**
* leetCode: 2280
* 时间复杂度:O(nlogn)
* 空间复杂度:O(1)
*/
class Solution {
// CORE: 斜率变化次数 (y2-y1)(x3-x2)=(y3-y2)(x2-x1)
public static int minimumLines(int[][] stockPrices) {
if (stockPrices == null) {
return 0;
}
int length = stockPrices.length;
if (length < 2) {
return 0;
}
int count = 1;
// 给定的点不一定按顺序,顺序混乱可能会算多
Arrays.sort(stockPrices, (o1, o2) -> o1[0] - o2[0]);
for (int i = 0; i < length-2; i++) {
int x0 = stockPrices[i+1][0] - stockPrices[i][0];
int y0 = stockPrices[i+1][1] - stockPrices[i][1];
int x1 = stockPrices[i+2][0] - stockPrices[i+1][0];
int y1 = stockPrices[i+2][1] - stockPrices[i+1][1];
if (y1 * x0 != x1 * y0) {
count++;
}
}
return count;
}
}
66、删除元素后求均值
// 题干:leetcode 1619 给你一个整数数组 arr ,请你删除最小 5% 的数字和最大 5% 的数字后,剩余数字的平均值。
import java.util.Arrays;
class Solution {
public double trimMean(int[] arr) {
Arrays.sort(arr);
int count=0;
int offset = (int) (arr.length * 0.05);
for (int i = offset; i < arr.length - offset; i++) {
System.out.print(arr[i] + " ");
count += arr[i];
}
// return sum / (n * 0.9); // TODO 0.9是固定的
return (double) count/(arr.length-offset*2);
}
}
67、删除链表的节点
/**
* 删除链表的节点
*
* 输入: head = [4,5,1,9], val = 5
* 输出: [4,1,9]
*/
public class DeleteNodeVal {
public ListNode deleteNode(ListNode head, int val) {
if (head == null) {
return null;
}
ListNode prev = null;
ListNode curr = head;
while (curr != null){
ListNode next = curr.next;
if (val == curr.val && prev != null) {
prev.next = next;
}
prev = curr;
curr = next;
}
while (head.val == val) {
head = head.next;
}
return head;
}
public static class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
}
68、链表中倒数第k个节点
public ListNode getKthFromEnd(ListNode head, int k) {
if (head == null || k <= 0) {
return null;
}
Stack<ListNode> stack = new Stack<>();
while (head != null) {
stack.push(head);
head = head.next;
}
for (int i = 0; i < k; i++) {
ListNode pop = stack.pop();
if (i == k - 1) {
return pop;
}
}
return null;
}
69、合并两个排序的链表
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode result = new ListNode(-1);
ListNode temp = result;
while (l1 != null && l2 != null) {
if (l1.val > l2.val) {
temp.next = l2;
l2 = l2.next;
} else {
temp.next = l1;
l1 = l1.next;
}
temp = temp.next;
}
if (l1 != null) {
temp.next = l1;
}
if (l2 != null) {
temp.next = l2;
}
return result.next;
}
70、调整数组/链表顺序使奇数位于偶数前面
public int[] exchange(int[] nums) {
// 双指针 时间复杂度O(n).空间复杂度O(1)
int i=0;
int j=nums.length-1;
while (i<j) {
while (i<j && nums[i] % 2 != 0) {
i++;
}
while (i<j && nums[j] % 2 != 1) {
j--;
}
if (i < j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
return nums;
// 双指针 时间复杂度O(n).空间复杂度O(n)
// int[] temp = new int[nums.length];
// int index=0;
//
// for (int i = 0; i < nums.length; i++) {
// if (nums[i] % 2 == 1) {
// temp[index]=nums[i];
// index ++;
// }
// }
// for (int i = 0; i < nums.length; i++) {
// if (nums[i] % 2 == 0) {
// temp[index]=nums[i];
// index ++;
// }
// }
//
// return temp;
}
/**
* 重拍链表奇偶,让奇数在前、偶数在后
*/
public static ListNode oddEvenList3(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode odd = head;
ListNode even = head.next;
ListNode evenHead = even;
while (even != null && even.next != null) {
odd.next = even.next;
odd = odd.next;
even.next = odd.next;
even = even.next;
}
odd.next = evenHead;
return head;
}
71、找出数组中重复的值
/**
* 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,
* 也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
*
* 输入:
* [2, 3, 1, 0, 2, 5, 3]
* 输出:2 或 3
*/
public int findRepeatNumber(int[] nums) {
// 时间复杂度O(n) 空间复杂度O(1)
// 条件是所有数字都在0~n-1范围内,如果要求空间复杂度O(1),就需要考虑这一因素
// 因此,元素i与nums[i]是一对一的,如果出现n个元素i对应一个索引i,即重复
for (int i = 0; i < nums.length; i++) {
if (nums[i] == i) {
continue;
}
if (nums[nums[i]] == nums[i]) {
return nums[i];
}
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp;
i--;
}
return -1;
}
72、在排序数组中查找数字
// 二分法 时间复杂度最好情况O(logn),最坏O(n)。空间复杂度o1
public static int search(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return 0;
}
// 1 先查询出target其中之一元素的索引
int left = 0;
int right = nums.length-1;
Integer index = null;
while (left <= right && index == null) {
int pivot = left + ((right-left) >> 1);
if (nums[pivot] == target) {
index = pivot;
} else if (nums[pivot] > target) {
right = pivot-1;
} else {
left = pivot + 1;
}
}
if (index == null) {
return 0;
}
// 排序数组,遍历索引前后计算target个数,不等target时可以直接break
int count = 0;
for (int i = index; i >= 0; i--) {
if (nums[i] == target) {
count ++;
} else {
break;
}
}
for (int i = index+1; i<nums.length; i++) {
if (nums[i] == target) {
count ++;
} else {
break;
}
}
return count;
}
73、从上到下打印二叉树
/**
* 广度优先搜索
* 例如:
* 给定二叉树: [3,9,20,null,null,15,7],
*
* 3
* / \
* 9 20
* / \
* 15 7
* 返回其层次遍历结果:
*
* [
* [3],
* [9,20],
* [15,7]
* ]
*/
public List<List<Integer>> levelOrder(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
// 存放最终结果
List<List<Integer>> list = new ArrayList<>();
// 存放已遍历完节点的子节点
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
// 暂存当前层的数据
List<Integer> temp = new ArrayList<>();
Queue<TreeNode> tempQueue = new LinkedList<>();
while (!queue.isEmpty()) {
TreeNode poll = queue.poll();
temp.add(poll.val);
if (poll.left != null) {
tempQueue.add(poll.left);
}
if (poll.right != null) {
tempQueue.add(poll.right);
}
}
list.add(temp);
queue = tempQueue;
}
return list;
}
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
74、镜像二叉树
/**
* 镜像二叉树 时间复杂度O(n) 空间复杂度O(1)
*
* 例如输入:
*
* 4
*
* / \
*
* 2 7
*
* / \ / \
*
* 1 3 6 9
* 镜像输出:
*
* 4
*
* / \
*
* 7 2
*
* / \ / \
*
* 9 6 3 1
*/
public TreeNode mirrorTree(TreeNode root) {
if (root == null) {
return root;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode poll = queue.poll();
TreeNode right = poll.right;
poll.right = poll.left;
poll.left = right;
if (poll.left != null) {
queue.offer(poll.left);
}
if (poll.right != null) {
queue.offer(poll.right);
}
}
return root;
}
// 递归算法 时间复杂度O(n) 空间复杂度O(n)
public TreeNode mirrorTree2(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = mirrorTree(root.left);
TreeNode right = mirrorTree(root.right);
root.left = right;
root.right = left;
return root;
}
75、二叉搜索树的第k大节点
/**
* 输入: root = [3,1,4,null,2], k = 1
* 3
* / \
* 1 4
* \
* 2
* 输出: 4
*
* 二叉搜索树的第 k 大节点
* 此树是中序遍历,即 左-根-右... 方式排序
* 时间复杂度O(n) 空间复杂度O(n)
*/
public int kthLargest(TreeNode root, int k) {
// 额外使用空间存储排序的k值
List<Integer> list = new ArrayList<>();
dfs(root, list, k);
return list.size() >= k ? list.get(k-1) : -1;
}
private void dfs(TreeNode root, List<Integer> list, int k) {
// 判空和提前返回
if (root == null || k == list.size()) {
return;
}
dfs(root.right, list, k);
list.add(root.val);
dfs(root.left, list, k);
}
76、接雨水
/**
* 题目:接雨水
* 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
* 输出:6
* 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
*
* 解法:动态规划、单调栈、双指针,后两种算法待补充
*/
/**
* 动态规划
* nums[i]能接到的水 = min(左边最高列,右边最高的列)-nums[i]的列高
*/
public static int trap1(int[] height) {
if (height == null || height.length == 0) {
return 0;
}
// 动态规划一: 时间复杂度O(n^2) 空间复杂度O(1)
// int count = 0;
// for (int i = 1; i < height.length-1; i++) {
//
// int left = 0;
// for (int p=0; p < i; p++) {
// left = Math.max(left, height[p]);
// }
// int right= 0;
// for (int q=height.length-1; q > i; q--) {
// right = Math.max(right, height[q]);
// }
//
// int num = Math.min(right, left)-height[i];
// count += Math.max(num, 0);
// }
// 动态规划一: 时间复杂度O(n) 空间复杂度O(n)
int[] leftMax = new int[height.length];
for (int i = 0; i < height.length; i++) {
if (i == 0) {
leftMax[i] = height[i];
} else {
leftMax[i] = Math.max(height[i], leftMax[i-1]);
}
}
int[] rightMax = new int[height.length];
for (int i = height.length-1; i >=0; i--) {
if (i == height.length-1) {
rightMax[i] = height[i];
} else {
rightMax[i] = Math.max(height[i], rightMax[i+1]);
}
}
int count=0;
for (int i = 1; i < height.length-1; i++) {
// leftMax,rightMax 取i不用处理sum等于负数情况
int sum = Math.min(leftMax[i], rightMax[i]) - height[i];
count += sum;
}
return count;
}
77、三个线程交替打印0-100
public static void main(String[] args) throws InterruptedException {
Thread worker1 = new Thread(new Worker(0));
Thread worker2 = new Thread(new Worker(1));
Thread worker3 = new Thread(new Worker(2));
worker1.start();
worker2.start();
worker3.start();
}
// 方法一:synchronized+wait/notify
public class Worker implements Runnable{
// static线程共享
private static Object lock = new Object();
private static int count = 0;
private int no;
public Worker(int no) {
this.no = no;
}
@Override
public void run() {
while (true) {
synchronized (lock) {
if (count > 100) {
break;
}
if (count % 3 == this.no) {
System.out.println(this.no + "--->" + count);
count++;
} else {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notifyAll();
}
}
}
}
// 方法二:重入锁
public class ReentrantLockWorker implements Runnable{
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static int count=0;
private int no;
public ReentrantLockWorker(int no) {
this.no = no;
}
@Override
public void run() {
lock.lock();
while (true) {
if (count > 100) {
break;
}
if (count % 3 == no) {
System.out.println(this.no + " ---> " + count);
count ++;
condition.signalAll();
} else {
try {
condition.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
lock.unlock();
}
}
78、重排链表
/**
* 给定一个单链表 L 的头节点 head ,单链表 L 表示为:
* L0 → L1 → … → Ln - 1 → Ln
* 请将其重新排列后变为:
* L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
*
* 线性表+双指针,时间复杂度On,空间复杂度On
*/
public static void reorderList(ListNode head) {
if (head == null || head.next == null) {
return;
}
List<ListNode> listNodes = new ArrayList<>();
while (head != null) {
listNodes.add(head);
head = head.next;
}
int left = 0;
int right = listNodes.size()-1;
while (left < right) {
listNodes.get(left).next = listNodes.get(right);
listNodes.get(right).next = listNodes.get(left+1);
left++;
right--;
}
listNodes.get(left).next = null;
}
未完待续。。。