1.整数除法
给定两个整数 a 和 b ,求它们的除法的商 a/b ,要求不得使用乘号 '*'、除号 '/' 以及求余符号 '%' 。
注意:整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
假设我们的环境只能存储 32 位有符号整数,其数值范围是 [−231, 231−1]。本题中,如果除法结果溢出,则返回 231 − 1
/**
* @description 犯错点: 1.补码表示 int ,负数
* 2.怎样int会溢出
* 3.除数扩大两倍,商扩大两倍
* @author PangTiemin
* @date 2021/11/12 17:39
*/
public int divide(int a, int b) {
//用补码计算int的最小值、最大值,-1
if(a==0x80000000&&b==0xffffffff){
return 0x7fffffff;
}
//两个数变为负数去计算触除法,如果两数都变为正数计算,负转正可能溢出
int c =2;
if(a>0){
a = -a;
c--;
}
if(b>0){
b=-b;
c--;
}
int result = 0;
while(a<=b){
int nb = b;
int s = 1;
//除数扩大两倍,如果被除数依然比除数大,则商也扩大两倍
while(nb>= 0xc0000000 &&a<=nb<<2){
nb = nb<<2;
s =s<<2;
}
result += s;
a -=nb;
}
if(c==1){
result = -result;
}
return result;
}
2二进制加法
给定两个 01 字符串 a
和 b
,请计算它们的和,并以二进制字符串的形式输出。
输入为 非空 字符串且只包含数字 1
和 0
。
public String addBinary(String a, String b) {
char[] achars = a.toCharArray();
char[] bchars = b.toCharArray();
int length = achars.length > bchars.length ? achars.length : bchars.length;
int carry = 0;
StringBuilder result = new StringBuilder();
for (int i = 0; i < length || carry > 0; i++) {
int ac = i < achars.length? achars[ achars.length - i - 1] - 48 : 0;
int bc = i < bchars.length ? bchars[bchars.length - i - 1] - 48 : 0;
int k = ac + bc + carry;
if (k > 1) {
carry = k / 2;
k = k % 2;
} else {
carry = 0;
}
result.append(k);
}
return result.reverse().toString();
}
3前n个数字二进制形式中1的个数
//第一种方法
public int[] countBits(int n) {
int[]result = new int[n+1];
for (int i = 0; i < n + 1; i++) {
int y=0;
int l = i;
while (l!=0){
y++;
l=l&l-1;
}
result[i]=y;
}
return result;
}
//第二种方法,由第一种方法延伸,动态规划的味道
public int[] countBits2(int n) {
//f(n)=1+f(n&n-1), f(n)指二进制n的1的个数
int[]result = new int[n+1];
for (int i = 1; i < n + 1; i++) {
result[i]=1+result[i&i-1];
}
return result;
}
//第三种方法,判断奇偶
public int[] countBits3(int n) {
//偶数f(n)=f(n/2),奇数f(n)=f(n)=f(n/2)+1
int[]result = new int[n+1];
for (int i = 1; i < n + 1; i++) {
result[i] = result[i/2]+(i&1);
}
return result;
}
4只出现一次的数字
给你一个整数数组 nums
,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。
/**
* @description 1. 1<<32 =1 位移要先取模
* 2. (-2 <<3)&1 答案是1,不要把 -2想成原码,他是补码 ,-4的补码会出乎你的意料
* 3. 我的方法没错,不管是不是补码 还是别的表现形式, 只要3倍 除3余数是0,符号位也一样,补码的符号位和原码一样!所以最后结果肯定正确
* 只是不能由字符串转二进制
* @author PangTiemin
* @date 2021/11/11 19:23
*/
public int singleNumber(int[] nums) {
int[] result = new int[32];
for (int i = 0; i < 32; i++) {
for (int num : nums) {
result[i] += num >> i & 1;
}
}
StringBuilder s = new StringBuilder();
int a = 0;
for (int i = 0; i < 32; i++) {
a=a+(result[i]%3<<i);
s.append(result[i]%3);
}
System.out.println(s.reverse().toString());
//字符串转二进制Integer.parseInt("110",2),32位会报错
return a;
}
5单次长度最大乘积
给定一个字符串数组 words,请计算当两个字符串 words[i] 和 words[j] 不包含相同字符时,它们长度的乘积的最大值。假设字符串中只包含英语的小写字母。如果没有不包含相同字符的一对字符串,返回 0。
/**
* @description 难点 怎么比较两个字符串有没有相同字符
* @author PangTiemin
* @date 2021/11/12 17:42
*/
public int maxProduct(String[] words) {
int[] wordint = new int[words.length];
for (int i=0;i<words.length;i++) {
for (char c :words[i].toCharArray()){
//这不能用+号,防止多个相同字母出现
wordint[i] |= 1<<(c-'a');
}
}
int max = 0;
for (int i=0;i<words.length;i++){
for (int j=i+1;j<words.length;j++){
if ((wordint[i]&wordint[j])==0){
max = Math.max(max,words[i].length()*words[j].length());
}
}
}
return max;
}
6.排序数组两数字之和
给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。
假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。
//双指针方向相反,左指针初始位置在索引0,右指针初始位置在索引随后
public int[] twoSum(int[] numbers, int target) {
int a = 0;
int b = numbers.length - 1;
while (numbers[a] + numbers[b] != target && a < b && b < numbers.length && 0 <= a) {
if (numbers[a] + numbers[b] > target) {
b--;
} else {
a++;
}
}
int[] result = new int[]{a, b};
return result;
}
7.数组中和位0的三个数
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a ,b ,c ,使得 a + b + c = 0 ?请找出所有和为 0 且 不重复 的三元组。
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
//先排序
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
//防止第一个数n重复
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int n = nums[i];
int left = i + 1;
int right = nums.length - 1;
//不必判断 left > i && right < nums.length - 1 ,left只能往右走。right只能左走
//要把所有情况遍历,知道left=right,同一个索引的数不能用两次
while (left < right) {
//防止第二个数重复
if (left > i + 1 && nums[left] == nums[left - 1]) {
left++;
} else if (nums[left] + nums[right] < -n) {
left++;
} else if (nums[left] + nums[right] > -n) {
right--;
} else {
ArrayList<Integer> one = new ArrayList<>();
one.add(n);
one.add(nums[left]);
one.add(nums[right]);
result.add(one);
left++;
right--;
}
}
}
return result;
}
8.和大于target的最短子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。 * <p> * 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。 * <p>
/**
* @description 正整数,方向相同的双指针. 代码太烂了,书代码更简洁
* @author PangTiemin
* @date 2021/11/17 9:54
*/
public int minSubArrayLen(int target, int[] nums) {
int left = 0, right = 0, result = nums.length+1;
int sum = nums[0];
while (left <= right && right < nums.length) {
if (result==1){
return result;
}
if (sum>=target){
result = Math.min(result,right-left+1);
if (left+1<nums.length)
//和减去 最左边已经加上的
sum -=nums[left++];
}else {
if (++right<nums.length)
//和加上 右边未加的
sum +=nums[right];
}
}
result=result==nums.length+1?0:result;
return result;
}
9.乘积小于k的子数组
给定一个正整数数组 nums
和整数 k
,请找出该数组内乘积小于 k
的连续的子数组的个数。
public int numSubarrayProductLessThanK(int[] nums, int k) {
int left = 0;
int product = 1;
int result = 0;
for (int right = 0;right<nums.length;right++){
//先固定右边界,算此右边界下 符合的数组个数
product *=nums[right];
while (left<=right){
if(product<k){
//当前left-right中的所有子数组,都符合,因为都是正整数,子数组乘积只会更小
result += right-left+1;
break;
}else {
product /=nums[left++];
}
}
}
return result;
}
10和为k的子数组
给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。 * 非正整数数组,不能通过移指针确定 子数组和会变小或变大
/**
* @description 给定一个整数数组和一个整数 k ,请找到该数组中和为 k 的连续子数组的个数。
* 非正整数数组,不能通过移指针确定 子数组和会变小或变大
* 时间复杂度O(n^2) 太慢
* @author PangTiemin
* @date 2021/11/17 11:06
*/
public int subarraySum2(int[] nums, int k) {
int[] sums = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
sums[i] = i == 0 ? nums[0] : sums[i - 1] + nums[i];
}
int result = 0;
//遍历一个数组的子数组,一个技巧,先确定右边界,然后逐个移动左边界
for (int right = 0; right < nums.length; right++) {
for (int left = 0; left <= right; left++) {
if (sums[right] - sums[left] + nums[left] == k) {
result++;
}
}
}
return result;
}
11.0和1相同的子数组
12左右两边子数组和相等
给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
public int pivotIndex(int[] nums) {
int[] sums = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
if (i == 0) {
sums[0] = nums[0];
continue;
}
sums[i] = sums[i - 1] + nums[i];
}
int result = -1;
for (int i = 0; i < nums.length; i++) {
if (sums[nums.length - 1] - sums[i] == sums[i] - nums[i]) {
result = i;
break;
}
}
return result;
}
13二维子矩阵的和
给定一个二维矩阵 matrix,以下类型的多个请求:
计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。
实现 NumMatrix 类:
NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回左上角 (row1, col1) 、右下角 (row2, col2) 的子矩阵的元素总和。
public class NumMatrix {
private int[][] sums;
public NumMatrix(int[][] matrix) {
if (matrix.length < 1 && matrix[0].length < 1) {
return;
}
this.sums = new int[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[i].length; j++) {
if (i == 0) {
if (j == 0) {
sums[i][j] = matrix[i][j];
} else {
sums[i][j] = matrix[i][j] + sums[i][j - 1];
}
} else {
if (j == 0) {
sums[i][j] = matrix[i][j] + sums[i - 1][j];
} else {
sums[i][j] = matrix[i][j] + sums[i - 1][j] + sums[i][j - 1] - sums[i - 1][j - 1];
}
}
}
}
for (int i = 0; i < sums.length; i++) {
for (int j = 0; j < sums[0].length; j++) {
System.out.print(sums[i][j] + ",");
}
System.out.println();
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
if (row1 > 0) {
if (col1 > 0) {
//减的时候,注意是 col1 - 1 row1 - 1
return this.sums[row2][col2] - this.sums[row2][col1 - 1] - this.sums[row1 - 1][col2] + this.sums[row1 - 1][col1 - 1];
} else {
return this.sums[row2][col2] - this.sums[row1 - 1][col2];
}
} else {
if (col1 > 0) {
return this.sums[row2][col2] - this.sums[row2][col1 - 1];
} else {
return this.sums[row2][col2];
}
}
}
public static void main(String[] args) {
int[][] n = new int[5][5];
n[0] = new int[]{3, 0, 1, 4, 2};
n[1] = new int[]{5, 6, 3, 2, 1};
n[2] = new int[]{1, 2, 0, 1, 5};
n[3] = new int[]{4, 1, 0, 1, 7};
n[4] = new int[]{1, 0, 3, 0, 5};
NumMatrix numMatrix = new NumMatrix(n);
System.out.println(numMatrix.sumRegion(2, 1, 4, 3));
}
14字符串中的变位词
s1 和 s2,写一个函数来判断 s2 是否包含 s1 的某个变位词。 * 变位词 指字母相同,但排列不同的字符串。
public boolean checkInclusion(String s1, String s2) {
if (s1.length()>s2.length()){
return false;
}
int[] r = new int[26];
for (int i = 0; i < s1.length(); i++) {
r[s1.charAt(i) - 'a']++;
r[s2.charAt(i) - 'a']--;
}
if (isZero(r)) {
return true;
}
int i = 0;
int j = s1.length();
while (j <s2.length()) {
r[s2.charAt(i++) - 'a']++;
r[s2.charAt(j++) - 'a']--;
if (isZero(r)) {
return true;
}
}
return false;
}
private Boolean isZero(int r[]) {
for (int i : r) {
if (i != 0) {
return false;
}
}
return true;
}
15 字符串中所有的变位词
给定两个字符串 s 和 p,找到 s 中所有 p 的 变位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 * 变位词 指字母相同,但排列不同的字符串。
public List<Integer> findAnagrams(String s, String p) {
List result = new ArrayList();
if (p.length()>s.length()){
return result;
}
int[] map = new int[26];
for (int i = 0; i < p.length(); i++) {
map[p.charAt(i)-'a']++;
map[s.charAt(i)-'a']--;
}
if (this.isZero(map))
result.add(0);
int i=0;
int j=p.length()-1;
//左右指针是 子数组的边界,子数组包含左右指针
while (j<s.length()-1){
map[s.charAt(i++)-'a']++;
map[s.charAt(++j)-'a']--;
if (this.isZero(map))
result.add(i);
}
return result;
}
16不含重复字符的最长子字符串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长连续子字符串 的长度。 * <p> * 0 <= s.length <= 5 * 104 * s 由英文字母、数字、符号和空格组成 ,不光是英文字母 这就是ASCII 可以用256大小数组
public int lengthOfLongestSubstring(String s) {
// s 由英文字母、数字、符号和空格组成 ,不光是英文字母 这就是ASCII 可以用256大小数组,
//一旦出现重复字符,就重新把左指针定位到 重复字符索引,哪个字符重复了,只能让左指针逐个移动,凡是包含重复字符的字符串必然重复,所以新字符串得重新开始
//记录max即可, 如何判断发现重复?最后一个字符 哈希表是2即可,也没必要遍历哈希表
int result = 0;
int i = 0;
HashMap<Character, Integer> map = new HashMap<Character, Integer>();
for (int k = 0; k < s.length(); k++) {
char c = s.charAt(k);
if (map.containsKey(c) && map.get(c) > 0) {
//出现重复字符
map.put(s.charAt(k), 2);
while (map.get(s.charAt(k)) >1) {
//左指针左移到不重复为止
map.put(s.charAt(i), map.get(s.charAt(i++)) - 1);
}
continue;
}
map.put(s.charAt(k), 1);
result = Math.max(result, k - i + 1);
}
return result;
}
17含有所有字符的最短字符串
给定两个字符串 s 和 t 。返回 s 中包含 t 的所有字符的最短子字符串。如果 s 中不存在符合条件的子字符串,则返回空字符串 "" 。 * * 如果 s 中存在多个符合条件的子字符串,返回任意一个。 *s 和 t 由英文字母组成
public String minWindow(String s, String t) {
String result = "";
if (t.length()>s.length()){
return result;
}
int[] letters = new int[256];
for (int i = 0; i < t.length(); i++) {
letters[t.charAt(i)]++;
letters[s.charAt(i)]--;
}
if (isZero(letters))
return s.substring(0,t.length());
int min=Integer.MAX_VALUE;
int j = t.length();
int i = 0;
while (j<s.length()){
letters[s.charAt(j++)]--;
while (this.isZero(letters)){
if (j-i<min){
result = s.substring(i,j);
min = j-i;
}
letters[s.charAt(i++)]++;
}
}
return result;
}
private Boolean isZero(int[]letters){
for (int i = 0; i < letters.length; i++) {
if (letters[i]>0){
return false;
}
}
return true;
}
18有效的回文
给定一个字符串 s
,验证 s
是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写。
本题中,将空字符串定义为有效的 回文串
public boolean isPalindrome(String s) {
String s1 = s.toLowerCase();
int i = 0;
int j = s.length() - 1;
while (i < j) {
if (!Character.isLetterOrDigit(s1.charAt(i))) {
i++;
} else if (!Character.isLetterOrDigit(s1.charAt(j))) {
j--;
} else if (s1.charAt(i++) != s1.charAt(j--))
return false;
}
return true;
}
public boolean isPalindrome2(String s) {
String s1 = s.toLowerCase();
if ((s.length() & 1) == 1) {
return this.isEqual(s1, s.length() / 2 - 1, s.length() / 2 + 1);
} else {
return this.isEqual(s1, s.length() / 2, s.length() / 2 + 1);
}
}
private Boolean isEqual(String s, int left, int right) {
while (left >= 0) {
if (s.charAt(left--) != s.charAt(right++)) {
return false;
}
}
return true;
}
19最多删除一个字符得到回文
给定一个非空字符串 s,请判断如果 最多 从字符串中删除一个字符能否得到一个回文字符串。
//如果s本身就是回文怎么办,如果长度奇数,删除中心点就是回文,如果偶数,那删除两个中心点任意一个也是回文
//即如果s是回文,那删除一个肯定是回文
public boolean validPalindrome(String s) {
int i = 0;
int j = s.length() - 1;
while (i < j) {
if (s.charAt(i) != s.charAt(j)) {
break;
} else {
i++;
j--;
}
}
return (i == j || i + 1 == j) || isPalindrome(s, i + 1, j) || isPalindrome(s, i, j - 1);
}
public boolean isPalindrome(String s, int start, int end) {
String s1 = s.toLowerCase();
int i = start;
int j = end;
while (i < j) {
if (!Character.isLetterOrDigit(s1.charAt(i))) {
i++;
} else if (!Character.isLetterOrDigit(s1.charAt(j))) {
j--;
} else if (s1.charAt(i++) != s1.charAt(j--))
return false;
}
return true;
}
20回文字符串的个数
给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。 * <p> * 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
public int countSubstrings(String s) {
int count = s.length();
//dp[i][j] i是右边界,j是左边界
boolean[][] dp = new boolean[s.length()][s.length()];
for (int i = 0; i < s.length(); i++ ) {
dp[i][i] = true;
}
//右边界
for (int right = 1; right < s.length(); right++) {
//左边界
for (int left = 0; left < right; left++) {
if (s.charAt(right) == s.charAt(left) && (right - left < 3 || dp[right - 1][left + 1])) {
dp[right][left] = true;
count++;
} else {
dp[right][left] = false;
}
}
}
return count;
}
21删除链表的倒数第n个结点
给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
public static class ListNode {
int val;
ListNode next;
ListNode() {
}
public ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
//采用哨兵,但遍历了两次
public ListNode removeNthFromEnd2(ListNode head, int n) {
ListNode sentinel = new ListNode();
sentinel.next = head;
ListNode temp = sentinel;
int length = 0;
while ((temp = temp.next) != null) {
length++;
}
//a是删除节点 前的 连表长度,即a后面的一个节点被删除
int a = length - n;
temp = sentinel;
//如果a==0,那temp正好是sentinel
while (a > 0) {
temp = temp.next;
a--;
}
ListNode delete = temp.next;
temp.next = delete.next == null ? null : delete.next;
delete.next = null;
return sentinel.next;
}
22链表环入口处
给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next 指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
public static class ListNode {
int val;
ListNode next;
ListNode() {
}
public ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode detectCycle(ListNode head) {
if (head==null||head.next==null){
return null;
}
//length是环节点数量
int cycleLength = this.cycleLength(head);
if (cycleLength==0){
return null;
}
ListNode left = head;
ListNode right = head;
//想让环头尾相撞,头尾间节点数量必须是 length+1
for (int i = 0; i < cycleLength; i++) {
right = right.next;
}
while (left!=right){
left = left.next;
right = right.next;
}
return left;
}
private int cycleLength(ListNode head) {
ListNode left = head;
ListNode right = head.next.next;
//快慢双指针找环中一点
while (left != right) {
left = left.next;
//判断如果不是一个环
if (right==null||right.next==null||right.next.next==null){
return 0;
}
right = right.next.next;
}
int length = 1;
left = left.next;
while (right != left) {
length++;
left = left.next;
}
//length是环节点数量,当right==left时,已经跳出while循环
return length;
}
23两个链表的第一个重合结点
给定两个单链表的头节点 headA 和 headB , * 请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。题目数据 保证 整个链式结构中不存在环。注意,函数返回结果后,链表必须 保持其原始结构 。
先构造环,用22题找到环的入口结点就是答案,再把环删除。
public Solution22.ListNode getIntersectionNode(Solution22.ListNode headA, Solution22.ListNode headB) {
if (!this.isIntersection(headA,headB)){
return null;
}
//把A首尾相连
Solution22.ListNode temp = headA;
while (temp.next != null) {
temp = temp.next;
}
temp.next = headA;
Solution22 solution22 = new Solution22();
Solution22.ListNode result = solution22.detectCycle(headB);
//题目要求保持链表原始结构
temp.next=null;
return result;
}
//判断是否相交
private Boolean isIntersection(Solution22.ListNode headA, Solution22.ListNode headB) {
Solution22.ListNode temp = headA;
HashSet<Solution22.ListNode> set = new HashSet<>();
while (temp != null) {
set.add(temp);
temp = temp.next;
}
Solution22.ListNode temp2 = headB;
while (temp2 != null) {
if (set.contains(temp2)){
return true;
}
temp2 = temp2.next;
}
return false;
}
public static void main(String[] args) {
int[] arr1 = new int[]{4,1,8,4,5};
Solution22.ListNode head = new Solution22.ListNode(arr1[0]);
Solution22.ListNode temp = head;
Solution22.ListNode door = head;
for (int i = 1; i < arr1.length; i++) {
Solution22.ListNode node = new Solution22.ListNode(arr1[i]);
temp.next = node;
temp = node;
if (i==2){
door = node;
}
}
int[] arr2 = new int[]{5,6,1};
Solution22.ListNode head2 = new Solution22.ListNode(arr2[0]);
Solution22.ListNode temp2 = head2;
for (int i = 1; i < arr2.length; i++) {
Solution22.ListNode node = new Solution22.ListNode(arr2[i]);
temp2.next = node;
temp2 = node;
}
temp2.next = door;
Solution23 solution23 = new Solution23();
Solution22.ListNode intersectionNode = solution23.getIntersectionNode(head, head2);
System.out.println(intersectionNode.val);
}
24反转链表
给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。
用三个变量,pre,cur,next,一次循环全搞定。
public static class ListNode {
int val;
ListNode next;
ListNode() {
}
public ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
//方式很多1.递归 2.循环用三个临时变量 3.用一个栈
public ListNode reverseList(ListNode head) {
if (head==null){
return null;
}
return reverse(null,head,head.next);
}
private ListNode reverse(ListNode pre,ListNode cur,ListNode next){
cur.next=pre;
if (next==null){
return cur;
}else {
return reverse(cur,next,next.next);
}
}
25链表中两数相加
给定两个 非空链表 l1和 l2 来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
可以假设除了数字 0 之外,这两个数字都不会以零开头。
public static class ListNode {
int val;
ListNode next;
ListNode() {
}
public ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
//用栈 力扣 超出内存限制
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode n1 = reverseList(l1);
ListNode n2 = reverseList(l2);
int carry = 0;
ListNode n3 = null;
//还需要判断 carry,如 5+5 =10
while (n1 != null || n2 != null || carry != 0) {
int v1 = n1 == null ? 0 : n1.val;
int v2 = n2 == null ? 0 : n2.val;
int v3 = (v1 + v2 + carry) % 10;
carry = (v1 + v2 + carry) / 10;
ListNode temp = new ListNode(v3);
temp.next = n3;
n3 = temp;
n1 = n1 == null ? null : n1.next;
n2 = n2 == null ? null : n2.next;
}
return n3;
}
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode pre = null;
ListNode cur = head;
ListNode next;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
26重排链表
给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln-1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
public static class ListNode {
int val;
ListNode next;
ListNode() {
}
public ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public void reorderList(ListNode head) {
if (head == null || head.next == null || head.next.next == null) {
return;
}
//把链表分为两个,进行合并,合并结果最后一个节点必须是第一个链表的
//如果偶数,两个链表一样长,奇数第一个链表多一个
ListNode left = head;
ListNode right = head;
while (right.next != null && right.next.next != null) {
left = left.next;
right = right.next.next;
}
ListNode l1 = head;
ListNode head2 = left.next;
left.next=null;
ListNode l2 = reverseList(head2);
while (l2!=null){
ListNode next1 = l1.next;
l1.next=l2;
ListNode next2 = l2.next;
l2.next=next1;
l2 = next2;
l1 = next1;
}
}
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode pre = null;
ListNode cur = head;
ListNode next;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
27回文链表
给定一个链表的 头节点 head ,请判断其是否为回文链表
public static class ListNode {
int val;
ListNode next;
ListNode() {
}
public ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
//和上一题 一个做法
public boolean isPalindrome(ListNode head) {
//分成两个链表,偶数两个长度相等,奇数第一个长
//快慢双指针 把链表等分
ListNode left = head;
ListNode right = head;
//左指针最后是 第一个链表的最后一个节点,右指针是第二个链表最后一个节点
while (right.next != null && right.next.next != null) {
left = left.next;
right = right.next.next;
}
ListNode l2 = left.next;
left.next = null;
ListNode head2 = reverseList(l2);
while (head != null && head2 != null) {
if (head.val!=head2.val){
return false;
}
head = head.next;
head2 = head2.next;
}
ListNode center = head == null ? head2 : head;
//center==null,说明是偶数 并且回文,center.next==null说明是奇数 并且回文
return center==null||center.next==null;
}
public ListNode reverseList(ListNode head) {
if (head == null) {
return null;
}
ListNode pre = null;
ListNode cur = head;
ListNode next;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
28展平多级双向列表
多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。 * <p> * 给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。
class Node {
public int val;
public Node prev;
public Node next;
public Node child;
public Node(int val) {
this.val = val;
}
}
//未知 有多少可重复的操作,明显递归
public Node flatten(Node head) {
children(null, head);
return head;
}
public Node children(Node parent, Node head) {
if (parent != null) {
parent.child = null;
parent.next = head;
head.prev = parent;
}
Node temp = head;
Node tail = temp;
while (temp != null) {
if (temp.child == null) {
tail = temp;
temp = temp.next;
} else {
Node next = temp.next;
Node childrenTail = children(temp, temp.child);
childrenTail.next = next;
if (next != null) {
next.prev = childrenTail;
}
tail = childrenTail;
temp = next;
}
}
return tail;
}
29排序的循环链表
给定循环单调非递减列表中的一个点,写一个函数向这个列表中插入一个新元素 insertVal ,使这个列表仍然是循环升序的。
给定的可以是这个列表中任意一个顶点的指针,并不一定是这个列表中最小元素的指针。
如果有多个满足条件的插入位置,可以选择任意一个位置插入新的值,插入后整个列表仍然保持有序。
如果列表为空(给定的节点是 null),需要创建一个循环有序列表并返回这个节点。否则。请返回原先给定的节点。
static class Node {
public int val;
public Node next;
public Node() {
}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _next) {
val = _val;
next = _next;
}
}
public Node insert(Node head, int insertVal) {
//环中没有节点
if (head == null) {
Node node = new Node(insertVal);
node.next = node;
return node;
}
Node temp = head;
Node max = new Node(Integer.MIN_VALUE);
while (temp != null) {
//必须是<= 防止有重复最大节点
max = max.val<=temp.val?temp:max;
Node next = temp.next;
if (next != null && temp.val <= insertVal && insertVal <= next.val) {
//正好有 左小右大的位置插入
Node node = new Node(insertVal);
temp.next = node;
node.next = next;
return head;
} else if (next == null) {
//万一不是环
Node node = new Node(insertVal);
temp.next = node;
node.next = head;
return head;
} else if (next == head) {
//遍历一圈没有能插入的地方,说明insertVal比所有节点小或比所有节点大,就插入到max后, 因为是升序的
Node node = new Node(insertVal);
Node min = max.next;
node.next = min;
max.next = node;
return head;
} else {
//继续遍历
temp = temp.next;
}
}
return head;
}
public static void main(String[] args) {
int[] a = new int[]{1, 3, 3};
Node head = null;
Node temp = null;
for (int i = 0; i < a.length; i++) {
Node node = new Node(a[i]);
if (temp != null) {
temp.next = node;
}
if (i == 0) {
head = node;
}
temp = node;
if (i == a.length - 1) {
node.next = head;
}
}
Solution29 solution29 =new Solution29();
System.out.println(solution29.insert(head,4));
}
30插入、删除、随机访问时间复杂度都是O(1)的容器
设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构: * * insert(val):当元素 val 不存在时返回 true ,并向集合中插入该项,否则返回 false 。 * remove(val):当元素 val 存在时返回 true ,并从集合中移除该项,否则返回 false 。 * getRandom:随机返回现有集合中的一项。每个元素应该有 相同的概率 被返回。
不难想到前两个要求hashmap,随机用数组,关键是在数组中删除 把最后一位元素放到删除元素的位置。
static class RandomizedSet {
private ArrayList<Integer> valList;
private HashMap<Integer,Integer> indexMap;
/** Initialize your data structure here. */
public RandomizedSet() {
valList=new ArrayList<>();
indexMap=new HashMap<>();
}
/** Inserts a value to the set. Returns true if the set did not already contain the specified element. */
public boolean insert(int val) {
if (indexMap.containsKey(val)){
return false;
}
valList.add(val);
indexMap.put(val,valList.size()-1);
return true;
}
/** Removes a value from the set. Returns true if the set contained the specified element. */
//难点,不能简单地remove
public boolean remove(int val) {
if (indexMap.containsKey(val)){
Integer index = indexMap.get(val);
//把最后一个元素移到删除位置
valList.set(index,valList.get(valList.size()-1));
//两个容器都是先 放 再删,否则可能报错,因为容器可能只有一个元素
//把map中的最后元素也修改位置
indexMap.replace(valList.get(index),index);
//删除list最后一个元素不会引起数组移动
valList.remove(valList.size()-1);
indexMap.remove(val);
return true;
}
return false;
}
/** Get a random element from the set. */
public int getRandom() {
Random random =new Random();
int index = random.nextInt(valList.size());
return valList.get(index);
}
}
public static void main(String[] args) {
RandomizedSet randomSet = new RandomizedSet(); // 初始化一个空的集合
// 返回 false,表示集合中不存在 2
System.out.println(randomSet.remove(0));
System.out.println(randomSet.remove(0));
// 向集合中插入 2 返回 true ,集合现在包含 [1,2]
System.out.println(randomSet.insert(0));
System.out.println(randomSet.remove(0));
System.out.println(randomSet.insert(0));
}
31最近最少使用缓存
运用所掌握的数据结构,设计和实现一个 LRU (Least Recently Used,最近最少使用) 缓存机制 。 * <p> * 实现 LRUCache 类: * <p> * LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存 * int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。 * void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 * <p>
/**
* @author PangTiemin
* @description 首先想到hashmap,只有他能保证O(1),
* 由上一题想到,hashmap key是值,value是数组索引,这道题也联想到value是 其他数据结构A的索引
* 数据结构A 用来存 真正的值,以最少被访问的顺序,如果用数组,每次访问后得移动元素以保证顺序,就不是O(n),
* 只有链表 在移动元素时是O(1),但链表 通过索引获取元素是O(n),hashmap的value就不能存链表索引,
* 所以 hashmap的value直接存 链表的节点 ,假设链表的头节点就是最少被访问的节点,尾结点是最近被访问的节点
* @date 2021/12/11 11:42
*/
static class LRUCache {
private HashMap<Integer, Node> nodeMap;
//head tail是哨兵节点,简化特殊判断
private Node head;
private Node tail;
private int capacity;
private int size;
class Node {
//key 作用,删除最少被访问的元素,链表容易删直接删头结点,但map 得知道key才能删
private int key;
private int value;
private Node next;
private Node pre;
public Node(int value) {
this.value = value;
}
public Node(int key, int value) {
this.key = key;
this.value = value;
}
}
public LRUCache(int capacity) {
this.capacity = capacity;
this.size = 0;
this.nodeMap = new HashMap();
this.head = new Node(-1);
this.tail=new Node(-1);
this.head.next=this.tail;
this.tail.pre=this.head;
}
public int get(int key) {
if (nodeMap.containsKey(key)) {
Node node = nodeMap.get(key);
this.moveNodeToTail(node);
return nodeMap.get(key).value;
}
return -1;
}
public void put(int key, int value) {
if (nodeMap.containsKey(key)){
Node node = nodeMap.get(key);
node.value=value;
this.moveNodeToTail(node);
}else {
if (size>=capacity){
this.deleteLeastVisitedNode();
}
Node node = new Node(key, value);
nodeMap.put(key,node);
Node tailPre = this.tail.pre;
tailPre.next=node;
node.pre=tailPre;
node.next=this.tail;
this.tail.pre=node;
this.size++;
}
}
//移动链表中的一个节点到尾结点
private void moveNodeToTail(Node node){
//本身是尾结点就不用移动
if (node.next!=tail){
Node pre = node.pre;
Node next = node.next;
pre.next=next;
next.pre=pre;
Node tailPre = this.tail.pre;
tailPre.next=node;
node.pre=tailPre;
node.next=this.tail;
this.tail.pre = node;
}
}
private void deleteLeastVisitedNode(){
Node node = this.head.next;
Node next = node.next;
this.head.next=next;
next.pre = this.head;
nodeMap.remove(node.key);
size--;
}
}
public static void main(String[] args) {
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // 缓存是 {1=1}
lRUCache.put(2, 2); // 缓存是 {1=1, 2=2}
lRUCache.get(1); // 返回 1
lRUCache.put(3, 3); // 该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2); // 返回 -1 (未找到)
lRUCache.put(4, 4); // 该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1); // 返回 -1 (未找到)
lRUCache.get(3); // 返回 3
lRUCache.get(4); // 返回 4
}
32有效的变位词
给定两个字符串 s 和 t ,编写一个函数来判断它们是不是一组变位词(字母异位词)。 * <p> * 注意:若 s 和 t 中每个字符出现的次数都相同且字符顺序不完全相同,则称 s 和 t 互为变位词(字母异位词)。
public boolean isAnagram(String s, String t) {
//s和t完全 一样 也不算 边变位词
if (s.length() != t.length() || s.equals(t)) {
return false;
}
HashMap<Character, Integer> map = new HashMap();
for (int i = 0; i < s.length(); i++) {
map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1);
map.put(t.charAt(i), map.getOrDefault(t.charAt(i), 0) - 1);
}
for (Character character : map.keySet()) {
if (!map.get(character).equals(0)) {
return false;
}
}
return true;
}
33变位词组
给定一个字符串数组 strs ,将 变位词 组合在一起。 可以按任意顺序返回结果列表。 * * 注意:若两个字符串中每个字符出现的次数都相同,则称它们互为变位词。
public List<List<String>> groupAnagrams(String[] strs) {
HashMap<String,List<String>> map = new HashMap();
for (String str : strs) {
char[] chars = str.toCharArray();
Arrays.sort(chars);
map.putIfAbsent(new String(chars),new ArrayList<String>());
map.get(new String(chars)).add(str);
}
List<List<String>> result = new ArrayList<>();
Set<String> keySet = map.keySet();
for (String s : keySet) {
result.add(map.get(s));
}
return result;
}
public static void main(String[] args) {
Solution33 solution33 = new Solution33();
String[] strs = new String[]{"eat", "tea", "tan", "ate", "nat", "bat"};
System.out.println(solution33.groupAnagrams(strs));
}
34外星字母排序
某种外星语也使用英文小写字母,但可能顺序 order 不同。字母表的顺序(order)是一些小写字母的排列。 * <p> * 给定一组用外星语书写的单词 words,以及其字母表的顺序 order,只有当给定的单词在这种外星语中按字典序排列时,返回 true;否则,返回 false。 * <p>
//O(mn)
public boolean isAlienSorted(String[] words, String order) {
//orderArray 哈希表表示字母表顺序
int[] orderArray = new int[order.length()];
for (int i = 0; i < order.length(); i++) {
orderArray[order.charAt(i) - 'a'] = i;
}
for (int i = 0; i < words.length - 1; i++) {
if (!this.isSorted(words[i], words[i + 1], orderArray)) {
return false;
}
}
return true;
}
//判断word1比word2小
private Boolean isSorted(String word1, String word2, int[] orderArray) {
for (int i = 0; i < word1.length() && i < word2.length(); i++) {
char c1 = word1.charAt(i);
char c2 = word2.charAt(i);
if (orderArray[c1 - 'a'] < orderArray[c2 - 'a']) {
return true;
} else if (orderArray[c1 - 'a'] > orderArray[c2 - 'a']) {
return false;
}
}
return word1.length() <= word2.length();
}
35最小时间差
给定一个 24 小时制(小时:分钟 "HH:MM")的时间列表,找出列表中任意两个时间的最小时间差并以分钟数表示。
//输入的时间数组是无序的,如果无序 必须得全部互相减一次才能比较出最小,时间O(n^2)
//所以想到把数组 先变为有序,这样最小值肯定在 两个相邻的元素间,不要忘了第一个元素和最后一个元素也是相邻时间。
// 排序一般是O(nlogn),相邻比较一圈是O(n),总时间O(nlogn)
// 注意,时间差值有两种计算,如23点和1点,一种是23-1,另一种是24-23+1-0=2,想钟表盘(假设一个表盘有24个点,不是12个点),有正时针和逆时针两个方向的差值
public int findMinDifference(List<String> timePoints) {
int timeEnd = 24 * 60;
//把时间换成数字表示
int[] timeArray = new int[timePoints.size()];
for (int i = 0; i < timePoints.size(); i++) {
String[] split = timePoints.get(i).split(":");
timeArray[i] = Integer.parseInt(split[0]) * 60 + Integer.parseInt(split[1]);
}
Arrays.sort(timeArray);
int min = Integer.MAX_VALUE;
for (int i = 0; i < timeArray.length; i++) {
if (i == timeArray.length - 1) {
min = Math.min(min, timeDiffer(timeArray[timeArray.length-1],timeArray[0],timeEnd));
continue;
}
min = Math.min(min,timeDiffer(timeArray[i+1],timeArray[i],timeEnd));
}
return min;
}
private int timeDiffer(int big, int small,int timeEnd) {
return Math.min(big - small, timeEnd - big + small);
}
36后缀表达式
根据 逆波兰表示法,求该后缀表达式的计算结果。 * * 有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。 * 整数除法只保留整数部分。 * 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。 * * 输入:tokens = ["2","1","+","3","*"] * 输出:9 * 解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
//代码不如书上简洁,
public int evalRPN(String[] tokens) {
Stack<String> stack = new Stack<String>();
for (String token : tokens) {
if (this.isOperator(token)){
//num1 num2是有顺序的,((2 + 1) * 3) = 9 2先入栈,1后入栈,所以2是运算符的左边,1是运算符右边
String num1 = stack.pop();
String num2 = stack.pop();
stack.push(this.cal(num2,num1,token));
}else {
stack.push(token);
}
}
return Integer.parseInt(stack.pop());
}
private String cal(String a,String b,String token){
int result=0;
switch (token){
case "+":
result=Integer.parseInt(a)+Integer.parseInt(b);
break;
case "-":
result=Integer.parseInt(a)-Integer.parseInt(b);
break;
case "*":
result=Integer.parseInt(a)*Integer.parseInt(b);
break;
case "/":
result=Integer.parseInt(a)/Integer.parseInt(b);
break;
}
return String.valueOf(result);
}
37小行星碰撞
给定一个整数数组 asteroids,表示在同一行的小行星。 * <p> * 对于数组中的每一个元素,其绝对值表示小行星的大小,正负表示小行星的移动方向(正表示向右移动,负表示向左移动)。每一颗小行星以相同的速度移动。 * <p> * 找出碰撞后剩下的所有小行星。碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。 * <p>
public int[] asteroidCollision(int[] asteroids) {
Stack<Integer> stack = new Stack<>();
for (int asteroid : asteroids) {
this.collision(asteroid, stack);
}
int[] result = new int[stack.size()];
for (int i = stack.size() - 1; i >= 0; i--) {
result[i] = stack.pop();
}
return result;
}
private void collision(int asteroid, Stack<Integer> stack) {
if (stack.isEmpty()) {
stack.push(asteroid);
} else {
//两个数字正负四种情况,只有前一个正数向右,后一个负数向左 才会碰撞,
if (stack.peek() > 0 && asteroid < 0) {
Integer pop = stack.pop();
//碰撞结果是小的爆炸,大的没事,如果大小相同,就全爆炸,不再入栈
Integer collisionResult = Math.abs(pop) > Math.abs(asteroid) ?
pop : (Math.abs(pop) < Math.abs(asteroid) ? asteroid : 0);
if (collisionResult!=0){
this.collision(collisionResult, stack);
}
} else {
stack.push(asteroid);
}
}
}
38每日温度
请根据每日 气温 列表 temperatures ,重新生成一个列表, * 要求其对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。 * 如果气温在这之后都不会升高,请在该位置用 0 来代替。 * <p>
public int[] dailyTemperatures(int[] temperatures) {
int[] result = new int[temperatures.length];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < temperatures.length; i++) {
while (!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
Integer index = stack.pop();
result[index] = i - index;
}
stack.push(i);
}
return result;
}
39直方图的最大矩形面积
给定非负整数数组 heights ,数组中的数字用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 * <p> * 求在该柱状图中,能够勾勒出来的矩形的最大面积。
public int largestRectangleArea(int[] heights) {
int max = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < heights.length; i++) {
while (!stack.isEmpty()&&heights[stack.peek()]>heights[i]){
Integer pop = stack.pop();
int left = stack.isEmpty()?-1:stack.peek();
int area = (i - left - 1) * heights[pop];
max = Math.max(max, area);
}
stack.push(i);
}
int last=stack.isEmpty()?0:stack.peek();
while (!stack.isEmpty()){
Integer pop = stack.pop();
if (stack.isEmpty()){
max =Math.max(max, heights[pop]*heights.length);
}else {
max =Math.max(max, (last-stack.peek())*heights[pop]);
}
}
return max;
}
public static void main(String[] args) {
int[]heights = new int[]{2,1,5,6,2,3};
Solution39S solution39 = new Solution39S();
System.out.println(solution39.largestRectangleArea(heights));
}
40矩阵中的最大矩形
给定一个由 0 和 1 组成的矩阵 matrix ,找出只包含 1 的最大矩形,并返回其面积。 * <p> * 注意:此题 matrix 输入格式为一维 01 字符串数组。
public int maximalRectangle(String[] matrix) {
if (matrix.length == 0) {
return 0;
}
int max = 0;
int[] heights = new int[matrix[0].length()];
for (String row : matrix) {
for (int i = 0; i < row.length(); i++) {
if (row.charAt(i) == '1') {
//1,则说明可以跟上方行的矩形相连
heights[i]++;
}
if (row.charAt(i) == '0') {
//有一个为0,和上面的矩形就无法相连,直方图就是0
heights[i] = 0;
}
}
max = Math.max(max, largestRectangleArea(heights));
}
return max;
}
public int largestRectangleArea(int[] heights) {
int rowMax = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < heights.length; i++) {
//栈中存放 还没有解决问题的元素;
// 这有个 >=问题,如果两个矩形高度相等?出栈、入栈都可以,因为总有一个矩形能正确计算出最大面积
while (!stack.isEmpty() && heights[stack.peek()] > heights[i]) {
Integer pop = stack.pop();
//矩形左边界,矩形不包含左边界; 为啥是-1?如果栈里没有元素了,说明pop就是目前遍历为止最低的高,但凡有比他低的肯定存在于栈中
int left = stack.isEmpty() ? -1 : stack.peek();
//以pop 为顶的矩形面积,目前高中属他最高;矩形右边界是i,当然也不包含右边界
int rectangle = heights[pop] * (i - left - 1);
rowMax = Math.max(rowMax, rectangle);
}
stack.push(i);
}
//执行结束后,还可能剩一些,高递增数组,因为递增,所有矩形 右边界都是最后这个元素; +1 为了保证矩形不包含右边界,和上面保持一致
Integer right = stack.peek() + 1;
while (!stack.isEmpty()){
Integer pop = stack.pop();
int left = stack.isEmpty() ? -1 : stack.peek();
int rectangle = heights[pop] * (right - left - 1);
rowMax = Math.max(rowMax, rectangle);
}
return rowMax;
}
41滑动窗口平均值
给定一个整数数据流和一个窗口大小,根据该滑动窗口的大小,计算滑动窗口里所有数字的平均值。 * <p> * 实现 MovingAverage 类: * <p> * MovingAverage(int size) 用窗口大小 size 初始化对象。 * double next(int val) 成员函数 next 每次调用的时候都会往滑动窗口增加一个整数,请计算并返回数据流中最后 size 个值的移动平均值,即滑动窗口里所有数字的平均值。 * <p>
class MovingAverage {
private int size;
private LinkedList<Integer> queue;
private double sum;
public MovingAverage(int size) {
this.size = size;
this.queue = new LinkedList<Integer>();
}
public double next(int val) {
queue.offer(val);
sum += val;
if (queue.size() > this.size) {
sum -= queue.poll();
}
return sum / queue.size();
}
}
42最近请求次数
请实现 RecentCounter 类: * * RecentCounter() 初始化计数器,请求数为 0 。 * int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。 * 保证 每次对 ping 的调用都使用比之前更大的 t 值。
class RecentCounter {
private LinkedList<Integer> queue;
public RecentCounter() {
this.queue=new LinkedList<Integer>();
}
public int ping(int t) {
queue.offer(t);
while (t - queue.peek()>3000){
queue.poll();
}
return queue.size();
}
}
43往完全二叉树添加节点
完全二叉树是每一层(除最后一层外)都是完全填充(即,节点数达到最大,第 n 层有 2n-1 个节点)的,并且所有的节点都尽可能地集中在左侧。 * <p> * 设计一个用完全二叉树初始化的数据结构 CBTInserter,它支持以下几种操作: * <p> * CBTInserter(TreeNode root) 使用根节点为 root 的给定树初始化该数据结构; * CBTInserter.insert(int v) 向树中插入一个新节点,节点类型为 TreeNode,值为 v 。使树保持完全二叉树的状态,并返回插入的新节点的父节点的值; * CBTInserter.get_root() 将返回树的根节点。
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;
}
}
class CBTInserter {
private TreeNode root;
private LinkedList<TreeNode> queue;
public CBTInserter(TreeNode root) {
if (root == null) {
return;
}
this.root=root;
queue=new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode peek = queue.peek();
if (peek.left != null && peek.right != null) {
queue.poll();
}
if (peek.left != null) {
queue.offer(peek.left);
}
if (peek.right != null) {
queue.offer(peek.right);
}
if (peek.left==null||peek.right==null){
return;
}
}
}
public int insert(int v) {
if (root == null) {
return -1;
}
TreeNode node = new TreeNode(v);
this.queue.offer(node);
TreeNode peek = this.queue.peek();
if (peek.left == null) {
peek.left = node;
return peek.val;
} else {
peek.right = node;
this.queue.poll();
return peek.val;
}
}
public TreeNode get_root() {
return this.root;
}
}
44二叉树每层最大值
给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
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;
}
}
//仅仅是少了两次判断,居然用时击败率提升 从13 到90%
public List<Integer> largestValues(TreeNode root) {
List<Integer> result = new LinkedList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue1 = new LinkedList();
Queue<TreeNode> queue2 = new LinkedList();
queue1.offer(root);
int max = Integer.MIN_VALUE;
while (!queue1.isEmpty()) {
TreeNode poll = queue1.poll();
max = Math.max(max, poll.val);
if (poll.left != null)
queue2.offer(poll.left);
if (poll.right != null)
queue2.offer(poll.right);
if (queue1.isEmpty()) {
result.add(max);
max = Integer.MIN_VALUE;
Queue temp = queue1;
queue1 = queue2;
queue2 = temp;
}
}
return result;
}
45二叉树最底层最左边的值
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;
}
}
//不适合用前序遍历,因为根节点的左树 不一定含有最底层节点
public int findBottomLeftValue(TreeNode root) {
TreeNode node = root;
Queue<TreeNode> queue1 = new LinkedList<>();
Queue<TreeNode> queue2 = new LinkedList<>();
queue1.offer(root);
while (!queue1.isEmpty()) {
TreeNode poll = queue1.poll();
if (poll.left != null)
queue2.offer(poll.left);
if (poll.right != null)
queue2.offer(poll.right);
if (queue1.isEmpty()) {
queue1 = queue2;
queue2 = new LinkedList<>();
if (!queue1.isEmpty())
node = queue1.peek();
}
}
return node.val;
}
46二叉树的右侧视图
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
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;
}
}
public List<Integer> rightSideView(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) {
return result;
}
Queue<TreeNode> queue1 = new LinkedList();
Queue<TreeNode> queue2 = new LinkedList();
queue1.offer(root);
while (!queue1.isEmpty()) {
TreeNode poll = queue1.poll();
if (poll.left!=null)
queue2.offer(poll.left);
if (poll.right!=null)
queue2.offer(poll.right);
if (queue1.isEmpty()){
//最右侧节点
result.add(poll.val);
queue1 = queue2;
queue2 = new LinkedList<>();
}
}
return result;
}
47二叉树剪枝
给定一个二叉树 根节点 root ,树的每个节点的值要么是 0,要么是 1。请剪除该二叉树中所有节点的值为 0 的子树。 * <p> * 节点 node 的子树为 node 本身,以及所有 node 的后代。
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;
}
}
public TreeNode pruneTree(TreeNode root) {
Boolean rootIsZero = this.proDfs(root);
return rootIsZero ? null : root;
}
private Boolean proDfs(TreeNode node) {
if (node == null)
return true;
Boolean left = proDfs(node.left);
Boolean right = proDfs(node.right);
if (left) {
node.left = null;
}
if (right)
node.right = null;
if (left && right && node.val == 0) {
return true;
}
return false;
}
48序列化与反序列化
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。 * <p> * 请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。 * <p>
public static 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;
}
}
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
String[] result = {"-1"};
if (root == null)
return result[0];
this.serializePreDfs(root, result);
return result[0];
}
private void serializePreDfs(TreeNode node, String[] result) {
if (node == null) {
result[0] = result[0] + ",#";
return;
}
result[0] = result[0] + "," + node.val;
this.serializePreDfs(node.left, result);
this.serializePreDfs(node.right, result);
}
// Decodes your encoded data to tree.,这就是在用 中序遍历 生成一个树, 可以作为生成树的构造方法。
public TreeNode deserialize(String data) {
String[] split = data.split(",");
if (split.length<2){
return null;
}
LinkedList<String> list = new LinkedList();
for (int i = 1; i < split.length; i++) {
list.add(split[i]);
}
return this.deserializePreDfs(list);
}
public TreeNode deserializePreDfs(LinkedList<String> queue) {
String poll = queue.poll();
TreeNode node;
if (poll.equals("#")) {
node = null;
} else {
node = new TreeNode(Integer.valueOf(poll));
node.left = deserializePreDfs(queue);
node.right = deserializePreDfs(queue);
}
return node;
}
public static void main(String[] args) {
TreeNode node1=new TreeNode(1);
TreeNode node2=new TreeNode(2);
TreeNode node3=new TreeNode(3);
TreeNode node4=new TreeNode(4);
TreeNode node5=new TreeNode(5);
node1.left=node2;
node1.right=node3;
node3.left=node4;
node3.right=node5;
Solution48 solution48 = new Solution48();
String serialize = solution48.serialize(node1);
System.out.println(solution48.deserialize(serialize));
}
74合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
public int[][] merge(int[][] intervals) {
Arrays.sort(intervals, Comparator.comparingInt(a -> a[0]));
LinkedList<int[]> list = new LinkedList<int[]>();
for (int[] interval : intervals) {
if (list.isEmpty()) {
list.add(interval);
} else {
int[] peek = list.peekLast();
if (peek[1] >= interval[0]) {
int[] a = new int[]{peek[0], Math.max(peek[1], interval[1])};
list.pollLast();
list.offer(a);
} else {
list.offer(interval);
}
}
}
int[][] ints = new int[list.size()][2];
return list.toArray(ints);
}
49从根节点到叶节点路径数字之和
给定一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。 * <p> * 每条从根节点到叶节点的路径都代表一个数字: * <p> * 例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。 * 计算从根节点到叶节点生成的 所有数字之和 。 * <p> * 叶节点 是指没有子节点的节点。 * <p>
public static 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;
}
}
//这道题不方便用 迭代方式,因为 往下层遍历需要传递 当前和,如果第n层计算完,返回n-1层,不知道n-1层和是多少,除非把node 的val改成当前路径的和
public int sumNumbers(TreeNode root) {
int[] sum = new int[1];
int one = 0;
dfsPre(root, sum, one);
return sum[0];
}
private void dfsPre(TreeNode node, int[] sum, int one) {
if (node == null) {
return;
}
if (node.left == null && node.right == null) {
sum[0] = sum[0] + one * 10 + node.val;
return;
}
dfsPre(node.left, sum, one * 10 + node.val);
dfsPre(node.right, sum, one * 10 + node.val);
}
50向下路径节点之和
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 * <p> * 路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。 * <p>
//这题和上一题一样,不适合迭代,因为往子节点要传递当前路径和,遍历完左子节点后,返回当前节点,想遍历右子节点,和要求是当前节点和,不能算上左节点
public int pathSum(TreeNode root, int targetSum) {
HashMap<Integer, Integer> sumMap = new HashMap();
sumMap.put(0, 1);
int[] count = new int[1];
dfsPre(root, sumMap, targetSum, count, 0);
return count[0];
}
public void dfsPre(TreeNode node, HashMap<Integer, Integer> sumMap, int targetSum, int[] count, int sum) {
if (node == null)
return;
sum += node.val;
count[0] += sumMap.getOrDefault(sum - targetSum, 0);
sumMap.put(sum, sumMap.getOrDefault(sum, 0) + 1);
dfsPre(node.left, sumMap, targetSum, count, sum);
dfsPre(node.right, sumMap, targetSum, count, sum);
sumMap.put(sum, sumMap.getOrDefault(sum, 0) - 1);
}
public static void main(String[] args) {
TreeNode node1 = new TreeNode(10);
TreeNode node2 = new TreeNode(5);
TreeNode node3 = new TreeNode(3);
node1.left = node2;
node2.left = node3;
Solution50 solution50 = new Solution50();
System.out.println(solution50.pathSum(node1, 8));
}
public static 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;
}
}
51节点之和的最大路径
路径 被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。 * <p> * 路径和 是路径中各节点值的总和。 * <p> * 给定一个二叉树的根节点 root ,返回其 最大路径和,即所有路径上节点值之和的最大值。 * <p>
//路径和的题目都适合 递归,不适合迭代
public int maxPathSum(TreeNode root) {
int[] max = new int[1];
//保证最小值
max[0]=Integer.MIN_VALUE;
dfsPro(root,max);
return max[0];
}
public int dfsPro(TreeNode node, int[] max) {
if (node == null)
return 0;
int left = dfsPro(node.left, max);
int right = dfsPro(node.right, max);
max[0] = Math.max(max[0], left + right + node.val);
left = Math.max(0, left + node.val);
left = Math.max(left, right + node.val);
//返回以当前遍历节点为路径起始点的最大值,如果子节点路径是负数 算0
return left;
}
public static 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;
}
}
52展平二叉搜索树
给你一棵二叉搜索树,请 按中序遍历 将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。
//TreeNode[] result 得数组,不能是普通引用 TreeNode result, 引用传递,如果是改引用对象的属性,原引用对象可以改变,如果把形参引用指向另一个对象,原引用不变
public TreeNode increasingBST(TreeNode root) {
TreeNode[] result = new TreeNode[1];
TreeNode[] last = new TreeNode[1];
dfsMid(root, result, last);
return result[0];
}
//不用想太复杂,中序遍历搜索树本来就是递增顺序,last记录上一个遍历的节点
private void dfsMid(TreeNode node, TreeNode[] result, TreeNode[] last) {
if (node == null) {
return;
}
//遍历左
dfsMid(node.left, result, last);
//遍历当前
if (result[0] == null) {
result[0] = node;
}
//遍历当前,说明当前节点的左节点处理了,不需要了
node.left = null;
//上个遍历的右节点 就是当前遍历的节点
if (last[0] != null)
last[0].right = node;
last[0] = node;
dfsMid(node.right, result, last);
}
53二叉搜索树中的中级后续
给定一棵二叉搜索树和其中的一个节点 p ,找到该节点在树中的中序后继。如果节点没有中序后继,请返回 null 。 * <p> * 节点 p 的后继是值比 p.val 大的节点中键值最小的节点,即按中序遍历的顺序节点 p 的下一个节点。 * <p>
//稍微改一下 二叉搜索树 查找函数。result越往下层找,肯定越小
public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
TreeNode node = root;
TreeNode result = null;
while (node != null) {
if (node.val <= p.val) {
node = node.right;
} else {
result = node;
node = node.left;
}
}
return result;
}
54所有大于等于节点的值之和
给定一个二叉搜索树,请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。 * <p> * 提醒一下,二叉搜索树满足下列约束条件: * <p> * 节点的左子树仅包含键 小于 节点键的节点。 * 节点的右子树仅包含键 大于 节点键的节点。 * 左右子树也必须是二叉搜索树。
//和路径和题目的区别!!! 路径和,节点传给 左、右 是同一个数字,这道题 传给 左右 是不同的数字,关键这个数字 依赖每次迭代去变化。这次迭代依赖上次迭代的值
//右-中-左,顺序。 用迭代,每一层用的是一个 和,在一个变量上加, 而不是每一层用不同的变量。
public TreeNode convertBST(TreeNode root) {
//栈 存放 没被处理 中和左 的节点
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
int sum = 0;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.right;
}
cur = stack.pop();
sum = sum + cur.val;
cur.val = sum;
cur = cur.left;
}
return root;
}
55二叉搜索树迭代器
实现一个二叉搜索树迭代器类BSTIterator ,表示一个按中序遍历二叉搜索树(BST)的迭代器: * <p> * BSTIterator(TreeNode root) 初始化 BSTIterator 类的一个对象。BST 的根节点 root 会作为构造函数的一部分给出。指针应初始化为一个不存在于 BST 中的数字,且该数字小于 BST 中的任何元素。 * boolean hasNext() 如果向指针右侧遍历存在数字,则返回 true ;否则返回 false 。 * int next()将指针向右移动,然后返回指针处的数字。 * 注意,指针初始化为一个不存在于 BST 中的数字,所以对 next() 的首次调用将返回 BST 中的最小元素。 * <p> * 可以假设 next() 调用总是有效的,也就是说,当调用 next() 时,BST 的中序遍历中至少存在一个下一个数字。
class BSTIterator {
private Stack<TreeNode> stack;
//cur 是 下一个要遍历的节点
TreeNode cur = null;
public BSTIterator(TreeNode root) {
stack = new Stack<>();
cur = root;
//cur 初始化为最小值,二叉搜索树的最左边节点
while (cur.left!=null){
stack.push(cur);
cur = cur.left;
}
}
//返回cur值后,cur要指向下一个节点。 下一个节点不一定是 cur的右节点,而是按中序顺序去找。 记住中序顺序,虽然和中序代码对应,但不用硬套
public int next() {
int result ;
if (cur==null)
cur = stack.pop();
result = cur.val;
cur = cur.right;
while (cur!=null){
stack.push(cur);
cur = cur.left;
}
return result;
}
//hasNext() 是中序遍历,while的条件
public boolean hasNext() {
return cur != null || !stack.isEmpty();
}
}
public static 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;
}
}
56二叉搜索树中两个节点之和
给定一个二叉搜索树的 根节点 root 和一个整数 k , 请判断该二叉搜索树中是否存在两个节点它们的值之和等于 k 。假设二叉搜索树中节点的值均唯一。
public boolean findTarget(TreeNode root, int k) {
BSTIterator bstIterator = new BSTIterator(root);
BSTIteratorReversed bstIteratorReversed = new BSTIteratorReversed(root);
int left = bstIterator.next();
int right = bstIteratorReversed.next();
while (left != right) {
if (left + right == k) {
return true;
} else if (left + right > k) {
right = bstIteratorReversed.next();
} else {
left = bstIterator.next();
}
}
return false;
}
//正向迭代器,从最小开始
class BSTIterator {
private Stack<TreeNode> stack;
//cur 是 下一个要遍历的节点
TreeNode cur = null;
public BSTIterator(TreeNode root) {
stack = new Stack<>();
cur = root;
//cur 初始化为最小值,二叉搜索树的最左边节点
while (cur.left != null) {
stack.push(cur);
cur = cur.left;
}
}
//返回cur值后,cur要指向下一个节点。 下一个节点不一定是 cur的右节点,而是按中序顺序去找。 记住中序顺序,虽然和中序代码对应,但不用硬套
public int next() {
int result;
if (cur == null)
cur = stack.pop();
result = cur.val;
cur = cur.right;
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
return result;
}
//hasNext() 是中序遍历,while的条件
public boolean hasNext() {
return cur != null || !stack.isEmpty();
}
}
//反向迭代,从最大开始
class BSTIteratorReversed {
private Stack<TreeNode> stack;
//cur 是 下一个要遍历的节点
TreeNode cur = null;
public BSTIteratorReversed(TreeNode root) {
stack = new Stack<>();
cur = root;
//cur 初始化为最大值,二叉搜索树的最右边节点
while (cur.right != null) {
stack.push(cur);
cur = cur.right;
}
}
//返回cur值后,cur要指向下一个节点。 下一个节点不一定是 cur的右节点,而是按中序顺序去找。 记住中序顺序,虽然和中序代码对应,但不用硬套
public int next() {
int result;
if (cur == null)
cur = stack.pop();
result = cur.val;
cur = cur.left;
while (cur != null) {
stack.push(cur);
cur = cur.right;
}
return result;
}
//hasNext() 是中序遍历,while的条件
public boolean hasNext() {
return cur != null || !stack.isEmpty();
}
}
public static 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;
}
}
75数组相对排序
arr2 中的元素各不相同 * arr2 中的每个元素都出现在 arr1 中 * 对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。 1 <= arr1.length, arr2.length <= 1000 0 <= arr1[i], arr2[i] <= 1000 arr2 中的元素 arr2[i] 各不相同 arr2 中的每个元素 arr2[i] 都出现在 arr1 中 1 <= arr1.length, arr2.length <= 1000 0 <= arr1[i], arr2[i] <= 1000 arr2 中的元素 arr2[i] 各不相同 arr2 中的每个元素 arr2[i] 都出现在 arr1 中
//关键是怎么形成 计数数组,数组索引代表一个待排序的元素,值代表元素的出现的次数
//这个计数数组 不一定是 题目要求的顺序
public int[] relativeSortArray(int[] arr1, int[] arr2) {
//计数数组
int[] count = new int[1001];
for (int i : arr1) {
count[i]++;
}
//i是重排arr1的索引
int i=0;
for (int v : arr2) {
//v是当前按顺序该出现的元素
while(count[v]>0){
arr1[i++]=v;
count[v]--;
}
}
//剩余元素
for (int j = 0; j < count.length; j++) {
while (count[j]>0){
arr1[i++]=j;
count[j]--;
}
}
return arr1;
}
76数组中第k大的数字
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 * <p> * 请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
public int findKthLargest(int[] nums, int k) {
return quickSort(nums, k, 0, nums.length - 1);
}
public int quickSort(int[] nums, int k, int start, int end) {
int random = new Random().nextInt(end - start + 1) + start;
swap(nums, random, end);
int p1 = start - 1;
int p2 = start;
while (p2 <= end) {
if (nums[p2] <= nums[end]) {
p1++;
swap(nums, p1, p2);
}
p2++;
}
if (p1 == (nums.length - k)) {
return nums[p1];
} else if (p1 < nums.length - k) {
return quickSort(nums, k, p1 + 1, end);
} else {
return quickSort(nums, k, start, p1 - 1);
}
}
public void swap(int[] nums, int index1, int index2) {
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
77链表排序
给定链表的头结点 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;
}
}
//把一条链表排序,先把它的两条子链表排序,再合二为一
public ListNode sortList(ListNode head) {
if (head==null||head.next == null)
return head;
ListNode head2 = split(head);
head = sortList(head);
head2 = sortList(head2);
return merge(head, head2);
}
public ListNode merge(ListNode head1, ListNode head2) {
ListNode sentinel = new ListNode();
ListNode next = sentinel;
while (head1 != null && head2 != null) {
if (head1.val > head2.val) {
next.next = head2;
head2 = head2.next;
} else {
next.next = head1;
head1 = head1.next;
}
next = next.next;
}
next.next = head1 == null ? head2 : head1;
return sentinel.next;
}
//一个链表,快慢双指针分为两个链表
public ListNode split(ListNode node) {
ListNode left = new ListNode();
left.next = node;
ListNode right = node;
while (right != null) {
left = left.next;
right = right.next;
if (right != null) {
right = right.next;
}
}
//把两个链表分开
ListNode next = left.next;
left.next = null;
return next;
}
78合并排序列表
给定一个链表数组,每个链表都已经按升序排列。 * <p> * 请将所有链表合并到一个升序链表中,返回合并后的链表。
public class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length==0){
return null;
}
return merge(lists, 0, lists.length - 1);
}
public ListNode merge(ListNode[] lists, int start, int end) {
if (start == end) {
return lists[start];
}
if (end - start == 1) {
return mergeTow(lists[start], lists[end]);
}
int mid = (start + end) / 2;
ListNode left = merge(lists, start, mid);
ListNode right = merge(lists, mid + 1, end);
return mergeTow(left, right);
}
public ListNode mergeTow(ListNode head1, ListNode head2) {
ListNode sentinel = new ListNode();
ListNode next = sentinel;
while (head1 != null && head2 != null) {
if (head1.val < head2.val) {
next.next = head1;
head1 = head1.next;
} else {
next.next = head2;
head2 = head2.next;
}
next = next.next;
}
if (head1 != null) {
next.next = head1;
} else {
next.next = head2;
}
return sentinel.next;
}
88爬楼梯的最小成本
数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。 * <p> * 每当爬上一个阶梯都要花费对应的体力值,一旦支付了相应的体力值,就可以选择向上爬一个阶梯或者爬两个阶梯。 * <p> * 请找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
/**
* @description cost[i] 是从i往上爬的成本,比如cost[1],是从1爬到2 或者1爬到3的成本,并不是爬到1的成本
* f(n)代表从n阶梯往上爬的总成本,具体爬到哪不管,反正往上爬就得付出cost[n],f(n)=min(f(n-1),f(n-2)) + cost[n]
* 如果n是顶楼,f(n-1) 和 f(n-2) 就是爬到顶楼的成本
* @author PangTiemin
* @date 2022/2/11 15:04
*/
public int minCostClimbingStairs(int[] cost) {
int[] dp = new int[2];
dp[0 % 2] = cost[0];
dp[1 % 2] = cost[1];
for (int i = 2; i < cost.length; i++) {
int prev1=dp[(i-1)%2];
int prev2=dp[(i-2)%2];
dp[i % 2] = Math.min(prev1,prev2)+cost[i];
}
return Math.min(dp[0],dp[1]);
}
89偷盗房屋
一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响小偷偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 * <p> * 给定一个代表每个房屋存放金额的非负整数数组 nums ,请计算 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
//f(n)代表第n个房子之前能偷到的最多钱,f(n)=min(f(n-1),nums[n]+f(n-2))
public int rob(int[] nums) {
if (nums.length < 2) {
return nums.length == 1 ? nums[0] : -1;
}
int[] dp = new int[2];
dp[0] = nums[0];
dp[1] = Math.max(nums[1], nums[0]);
for (int i = 2; i < nums.length; i++) {
int prev1 = dp[(i - 1) % 2];
int prev2 = dp[(i - 2) % 2];
dp[i % 2] = Math.max(prev1, prev2 + nums[i]);
}
return dp[(nums.length - 1) % 2];
}
90环形房屋偷盗
//如果偷0,最后一个就不能偷
public int rob(int[] nums) {
if (nums.length < 3) {
return nums.length == 1 ? nums[0] : Math.max(nums[0], nums[1]);
}
return Math.max(rob(nums, 0, nums.length - 2), rob(nums, 1, nums.length - 1));
}
public int rob(int[] nums, int start, int end) {
int[] dp = new int[2];
dp[start % 2] = nums[start];
dp[(start + 1) % 2] = Math.max(nums[start + 1],nums[start]);
for (int i = start + 2; i <= end; i++) {
int prev1 = dp[(i - 1) % 2];
int prev2 = dp[(i - 2) % 2];
dp[i % 2] = Math.max(prev1, prev2 + nums[i]);
}
return dp[end % 2];
}
91粉刷房屋
假如有一排房子,共 n 个,每个房子可以被粉刷成红色、蓝色或者绿色这三种颜色中的一种,你需要粉刷所有的房子并且使其相邻的两个房子颜色不能相同。 * <p> * 当然,因为市场上不同颜色油漆的价格不同,所以房子粉刷成不同颜色的花费成本也是不同的。每个房子粉刷成不同颜色的花费是以一个 n x 3 的正整数矩阵 costs 来表示的。 * <p> * 例如,costs[0][0] 表示第 0 号房子粉刷成红色的成本花费;costs[1][2] 表示第 1 号房子粉刷成绿色的花费,以此类推。 * <p> * 请计算出粉刷完所有房子最少的花费成本。
/**
* @description r(i) 把第i个房子粉刷成红色房子的最小成本,g(i)把第i个房子粉刷成绿色的最小成本,b(i)蓝色
* r(i)=cost[i][0]+Math.min(g(i-1),b(i-1))
* @author PangTiemin
* @date 2022/2/11 17:27
*/
public int minCost(int[][] costs) {
if (costs.length == 1) {
return Math.min(costs[0][0], Math.min(costs[0][1], costs[0][2]));
}
//dp[0][0] 第二维数组代表颜色,0红色,1蓝色,2绿色
int[][] dp = new int[2][3];
dp[0][0] = costs[0][0];
dp[0][1] = costs[0][1];
dp[0][2] = costs[0][2];
dp[1][0] = costs[1][0] + Math.min(dp[0][1], dp[0][2]);
dp[1][1] = costs[1][1] + Math.min(dp[0][0], dp[0][2]);
dp[1][2] = costs[1][2] + Math.min(dp[0][0], dp[0][1]);
for (int i = 2; i < costs.length; i++) {
dp[i % 2][0] = costs[i][0] + Math.min(dp[(i - 1) % 2][1], dp[(i - 1) % 2][2]);
dp[i % 2][1] = costs[i][1] + Math.min(dp[(i - 1) % 2][0], dp[(i - 1) % 2][2]);
dp[i % 2][2] = costs[i][2] + Math.min(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1]);
}
int l = costs.length - 1;
return Math.min(dp[l % 2][0], Math.min(dp[l % 2][1], dp[l % 2][2]));
}
93最长斐波拉契数列
如果序列 X_1, X_2, ..., X_n 满足下列条件,就说它是 斐波那契式 的: * <p> * n >= 3 * 对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2} * 给定一个严格递增的正整数数组形成序列 arr ,找到 arr 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。 * <p> * (回想一下,子序列是从原序列 arr 中派生出来的,它从 arr 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的一个子序列) * <p>
/**
* @description f(i, j)表示以i为结尾,j i 是斐波那契数列的长度,当X(k)+X(j)=X(i),f(i,j)=f(j,k)+1, 约定j<i
* @author PangTiemin
* @date 2022/2/11 17:54
*/
public int lenLongestFibSubseq(int[] arr) {
//用map保存数
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
map.put(arr[i], i);
}
int result = 2;
int[][] dp = new int[arr.length][arr.length];
for (int i = 0; i < arr.length; i++) {
dp[i][0] = 2;
}
for (int i = 2; i < arr.length; i++) {
for (int j = 1; j < i; j++) {
//从map可以判断是否有k,存在 k+j=i,如果没map,得在0-j之间遍历找k,当然也能做出来,或者二分法找
Integer k = map.getOrDefault(arr[i] - arr[j], -1);
dp[i][j] = k >= 0 && k < j ? dp[j][k] + 1 : 2;
result = Math.max(result, dp[i][j]);
}
}
return result > 2 ? result : 0;
}
94最少回文分割
给定一个字符串 s,请将 s 分割成一些子串,使每个子串都是回文串。 * <p> * 返回符合要求的 最少分割次数 。
/**
* @description f(i)=Math.min(f(j))+1,0<=j<i,并且S(j+1,i)是回文
* @author PangTiemin
* @date 2022/2/14 14:19
*/
public int minCut(String s) {
boolean[][] isPal = new boolean[s.length()][s.length()];
//isPal i是右边界,j是左边界
for (int i = 0; i < s.length(); i++) {
for (int j = 0; j <= i; j++) {
if (i == j) {
isPal[i][j] = true;
} else if (i == j + 1) {
isPal[i][j] = s.charAt(i) == s.charAt(j);
} else {
char left = s.charAt(i);
char right = s.charAt(j);
isPal[i][j] = left == right && isPal[i - 1][j + 1];
}
}
}
int[] dp = new int[s.length()];
dp[0]=0;
for (int i = 1; i < s.length(); i++) {
if (isPal[i][0]){
dp[i]=0;
}else {
int min=Integer.MAX_VALUE;
for (int j = 1; j <= i; j++) {
min = isPal[i][j]?Math.min(min,dp[j-1]):min;
}
dp[i]=min+1;
}
}
return dp[s.length()-1];
}
95最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。 * <p> * 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 * <p> * 例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 * 两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
public int longestCommonSubsequence(String text1, String text2) {
int[][] dp = new int[2][text2.length()];
for (int i = 0; i < text1.length(); i++) {
for (int j = 0; j < text2.length(); j++) {
if (i == 0) {
dp[i % 2][j] = text2.substring(0, j + 1).contains(text1.charAt(0) + "") ? 1 : 0;
} else if (j == 0) {
dp[i % 2][j] = text1.substring(0, i + 1).contains(text2.charAt(0) + "") ? 1 : 0;
} else if (text1.charAt(i) == text2.charAt(j)) {
dp[i % 2][j] = dp[(i - 1) % 2][j - 1] + 1;
} else {
dp[i % 2][j] = Math.max(dp[(i - 1) % 2][j], dp[i % 2][j - 1]);
}
}
}
return dp[(text1.length() - 1) % 2][text2.length() - 1];
}
public int longestCommonSubsequence2(String text1, String text2) {
int[] dp = new int[text2.length()];
for (int i = 0; i < text1.length(); i++) {
int prev = 0;
int cur=0;
for (int j = 0; j < text2.length(); j++) {
if (i == 0) {
dp[j] = text2.substring(0, j + 1).contains(text1.charAt(0) + "") ? 1 : 0;
} else if (j == 0) {
prev = dp[j];
dp[j] = text1.substring(0, i + 1).contains(text2.charAt(0) + "") ? 1 : 0;
} else if (text1.charAt(i) == text2.charAt(j)) {
cur = prev + 1;
prev = dp[j];
dp[j] =cur;
} else {
prev = dp[j];
dp[j] = Math.max(dp[j], dp[j - 1]);
}
}
}
return dp[text2.length() - 1];
}
98矩阵路劲的数目
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0) {
dp[j] = 1;
} else if (j == 0) {
dp[j] = 1;
} else {
dp[j] = dp[j] + dp[j - 1];
}
}
}
return dp[n - 1];
}
101分割等和子集
01背包问题
给定一个非空的正整数数组 nums
,请判断能否将这些数字分成元素和相等的两部分。
/**
* @description 函数f(i, j)表示前i个数和是否能为j, 前i个意思是 索引包含i-1 以下
* f(i,j)=f(i-1,j-nums[i]) 如果加num[i]
* f(i,j)=f(i-1,j) 如果不加num[i]
* 空间需要两行,如果要优化为一行,得从右到左遍历
* @author PangTiemin
* @date 2022/2/16 16:06
*/
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) {
sum += num;
}
if (sum%2==1){
return false;
}
int target = sum / 2;
boolean[][] dp = new boolean[2][target + 1];
//前i个意思是 索引包含i-1 以下 ,所以 dp[0][j] 都是false
dp[0][0] = true;
dp[1][0] = true;
for (int i = 1; i < nums.length; i++) {
for (int j = 1; j < target + 1; j++) {
dp[i % 2][j] = dp[(i - 1) % 2][j];
if (!dp[i % 2][j] && nums[i] <= j) {
dp[i % 2][j] = dp[(i - 1) % 2][j - nums[i]];
}
}
}
return dp[(nums.length - 1) % 2][target];
}
102加减的目标值
给定一个正整数数组 nums 和一个整数 target 。 * <p> * 向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 : * <p> * 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。 * 返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。 * <p>
/**
* @description 上一题是 可不可以,这道题是数目 p-q=target p+q=sum 2p=target+sum p=(target+sum)/2
* f(i,j) 前i个装满j个的方法数,包括i为0处的元素,如果一共i个数,f(i-1,t)就是答案
* f(i,j) = f(i-1,j-nums[i])+f(i-1,j) 1.加num[i],前提 j>=num[i] 2.不加num[i]
* f(i,0) = 1 当j=0
* f(i,j) = 0 当i=0,j>0
* @author PangTiemin
* @date 2022/2/16 16:36
*/
public int findTargetSumWays(int[] nums, int target) {
if (nums.length<1||(nums.length==1&&nums[0]<target)){
return 0;
}
int sum = 0;
for (int num : nums) {
sum += num;
}
//不可能分成等式
if (sum<target||(target+sum)%2==1){
return 0;
}
//背包大小
int k = (target + sum) / 2;
int[][] dp = new int[2][k + 1];
//i=0 f(i,j)赋值
if (nums[0] <= k) {
dp[0][nums[0]] = 1;
}
//注意,0处的元素如果是0,会有+0 -0两种
dp[0][0] = nums[0]==0?2:1;
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j <= k; j++) {
dp[i % 2][j] = dp[(i - 1) % 2][j];
if (j >= nums[i]) {
dp[i % 2][j] += dp[(i - 1) % 2][j - nums[i]];
}
}
}
return dp[(nums.length - 1) % 2][k];
}
103最少的硬币数目
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 * <p> * 你可以认为每种硬币的数量是无限的。
/**
* @description f(i, j)表示前i个元素(包含第0个)凑够j金额所需要的最少硬币数量,0<=i<=n-1,如果总共有n种硬币,那f(n-1,amout)就是答案
* f(i,j)=j>nums[0]&&j%nums[0]==0?j/nums[0]:Integer.MAX_VALUE 当i=0,j<=amount,Integer.MAX_VALUE表示不存在
* f(i,j)=Min(f(i-1,j),f(i-1,j-k*nums[i])+k), 0<=k<=amount/nums[i]
* @author PangTiemin
* @date 2022/2/17 15:34
*/
public int coinChange(int[] coins, int amount) {
int[][] dp = new int[2][amount + 1];
//填充最大值,如果填充0,f(i-1,j-k*nums[i])+k 会误以为是一次成功的分配方式
for (int i = 0; i < 2; i++) {
Arrays.fill(dp[i],Integer.MAX_VALUE);
}
//这是0,总额0需要0个硬币,跟上一题背包大小0有1种方法不一样
dp[0][0]=0;
//给f(i,j) 当i=0
int coin0 = coins[0];
int x = 1;
while (coin0 <= amount) {
dp[0][coin0] = x++;
coin0=coin0+coins[0];
}
if (coins.length==1){
return amount>0?dp[0][amount]==Integer.MAX_VALUE?-1:dp[0][amount]:0;
}
for (int i = 1; i < coins.length; i++) {
for (int j = 0; j <= amount; j++) {
dp[i % 2][j] = dp[(i - 1) % 2][j];
if (coins[i] <= j) {
for (int k = 1; k <= j / coins[i]; k++) {
if (Integer.MAX_VALUE!=dp[(i - 1) % 2][j - k * coins[i]]){
dp[i % 2][j] = Math.min(dp[i % 2][j], dp[(i - 1) % 2][j - k * coins[i]]+k);
}
}
}
}
}
return dp[(coins.length - 1) % 2][amount]==Integer.MAX_VALUE?-1:dp[(coins.length - 1) % 2][amount];
}
补充,图广度优先遍历
邻接矩阵表示图。顶点出队后要用visited判断是否访问过,即使在入队前判断未访问才入队,也得在出队后判断,因为同一层可能有两个顶点关联的点是重复的,导致一个顶点重复入队。
/**
* @description 广度优先,访问一个节点A后,依次访问与其相连的节点BC,再访问B C的相连节点
* @author PangTiemin
* @date 2022/2/20 21:03
*/
public void bfs(int[][] matrix) {
Queue<Integer> queue = new LinkedList<>();
boolean[] visited = new boolean[matrix.length];
//防止不连通的图
for (int i = 1; i < matrix.length; i++) {
if (!visited[i]) {
queue.offer(i);
}
//把一个连通的图,广度优先遍历
while (!queue.isEmpty()) {
Integer poll = queue.poll();
//出队后判断是否访问过
if (!visited[poll]) {
visited[poll] = true;
System.out.println("访问了节点" + poll);
for (int j = 1; j < matrix.length; j++) {
if (matrix[poll][j] == 1) {
//把一个节点的关联节点优先遍历,这里入队的可能是被访问过 的
queue.offer(j);
}
}
}
}
}
}
补充,深度优先遍历(栈)
把广度优先中的队列换成栈,就是栈深度优先遍历 。至于从顶点关联的左边点先遍历、还是右边点先 并不重要,有点像二叉树的中序遍历和逆先右后左的中序,总之不太重要、都是深度。
/**
* @description 深度优先是 与A相连有BC,访问A后,先访问B 然后B的相连节点D,再访问D的相连,如果B的相连都访问完,在访问C
* <p>
* 理论上跟二叉树的dfs一样,递归和迭代栈都可以
* <p>
* 迭代栈 只要把广度的 队列变成栈就可以了
* @author PangTiemin
* @date 2022/2/20 21:04
*/
public void dfsIter(int[][] matrix) {
Stack<Integer> stack = new Stack();
boolean[] visited = new boolean[matrix.length];
//防止不连通的图
for (int i = 1; i < matrix.length; i++) {
if (!visited[i]) {
stack.push(i);
}
//把一个连通的图,dfs深度遍历
while (!stack.isEmpty()) {
Integer pop = stack.pop();
//出队后判断是否访问过
if (!visited[pop]) {
visited[pop] = true;
System.out.println("访问了节点" + pop);
//反过来,让左边的优先深度遍历
for (int j = matrix.length - 1; j > 0; j--) {
if (matrix[pop][j] == 1) {
//把一个节点的关联节点优先遍历,这里入队的可能是被访问过 的
stack.push(j);
}
}
}
}
}
}
补充,深度优先(递归)
//i是遍历是谁
private void dfs(int[][] matrix, boolean[] visited, int i) {
if (!visited[i]) {
System.out.println("访问了" + i);
visited[i] = true;
for (int j = 0; j < matrix.length; j++) {
//依次找 与i相连的节点,发现一个a就递归找a相连的
if (matrix[i][j] == 1) {
dfs(matrix, visited, j);
}
}
}
}
补充,无权图最短路径
//无权图中的两点最短路径,只能求出距离,不能求出具体路径,用广度优先就可以,每经过一层距离+1
public int noRightShortPathBfs(int[][] matrix, int start, int end) {
//最短路径长度
int pathSum = 0;
Queue<Integer> queue1 = new LinkedList<>();
Queue<Integer> queue2 = new LinkedList<>();
queue1.offer(start);
boolean[] visited = new boolean[matrix.length];
while (!queue1.isEmpty()) {
Integer poll = queue1.poll();
if (!visited[poll]) {
visited[poll] = true;
if (poll == end) {
System.out.println(pathSum);
return pathSum;
}
for (int i = 0; i < matrix.length; i++) {
if (matrix[poll][i] == 1 && !visited[i]) {
queue2.offer(i);
}
}
}
if (queue1.isEmpty()) {
queue1 = queue2;
queue2 = new LinkedList<>();
pathSum++;
}
}
return -1;
}
补充,有权图最短路径
迪杰斯特拉
描述算法的习惯:
- 先描述变量含义,如visited是个List<Integer>集合,记录哪些顶点距离起点的位置已经确定;距离表int[] path数组记录起点到各顶点间的最小距离。
- 再描述算法,循环到list数量和顶点数量一样时退出循环,距离表中除已经确定最近位置的点以外、当前距离起点最近的点假设A,找A的相关联点,刷新距离表中这些关联点的距离,然后把A加入list,重复循环
/**
* @description 迪杰斯特拉算法 ,求两点间最短路径,适合有权图
* 假设起点A,终点F, 数组path[i] 表示A到i的 目前找到的最短距离,如果没找到用无穷大表示
* visited 记录已经找到最小距离的点
* A与BC相连,记录path[B],path[C]
* path[B],path[C] 中最小的那个如B, 找B相连的 DE,记录path[D]=min(path[D],path[B]+matrix[B][D]),path[E]=min(path[E],path[B]+matrix[B][E]),这就代表B的最小距离已经找到,加入visited
* 循环在距离表 的 未找到与起点最小距离的点中,找出与起点间最小距离的点B, 用这个点去找相邻点,找完代表path[B] 已经是最小,可以被visited记录访问过
* 一直循环到 所有边都被遍历过,怎样确定所有边都被遍历?记录数组,visited[i] 表示 i被访问过
* @author PangTiemin
* @date 2022/2/22 9:01
*/
public void dijkstra(int[][] matrix, int start, int end) {
//记录被访问过的节点
List<Integer> visited = new LinkedList<>();
//距离表,默认无穷大
int[] path = new int[matrix.length];
Arrays.fill(path, Integer.MAX_VALUE);
path[start] = 0;
while (visited.size() != matrix.length) {
//找当前距离表中,未访问完的顶点的 最小距离对应的点
int noVisitMinId = Integer.MAX_VALUE;
int noVisitMinPath = Integer.MAX_VALUE;
for (int i = 0; i < path.length; i++) {
if (!visited.contains(i) && path[i] != Integer.MAX_VALUE && path[i] < noVisitMinPath) {
noVisitMinId = i;
noVisitMinPath = path[i];
}
}
//把距离表中 非无穷大的点 、并且边没有被访问过的点 都去访问他们的相邻点
for (int j = 0; j < matrix.length; j++) {
//假设有权图里 不连通的边是0
//访问过的节点不再访问
if (matrix[noVisitMinId][j] != 0) {
int min = Math.min(path[j], path[noVisitMinId] + matrix[noVisitMinId][j]);
path[j] = min;
}
}
//所有的边都记录了,才算访问完了节点
visited.add(noVisitMinId);
}
System.out.println("从" + start + "到" + end + "距离" + path[end]);
}
补充,最小生成树
/**
* @description 普利姆算法,最小生成树,从A点开始,找与A最近的点如C,在从A C点找离他们最近的点如E,再从A C E找最近的点,一直把所有点都找到
* Prim算法简述
* 1).输入:一个加权连通图,其中顶点集合为V,边集合为E;
* 2).初始化:Vnew= {x},其中x为集合V中的任一节点(起始点),Enew= {},为空;
* 3).重复下列操作,直到Vnew= V:
* a.在集合E中选取权值最小的边<u, v>,其中u为集合Vnew中的元素,而v不在Vnew集合当中,并且v∈V(如果存在有多条满足前述条件即具有相同权值的边,则可任意选取其中之一);
* b.将v加入集合Vnew中,将<u, v>边加入集合Enew中;
* 4).输出:使用集合Vnew和Enew来描述所得到的最小生成树。 [1]
* @author PangTiemin
* @date 2022/2/21 11:16
*/
public void prim(int[][] matrix) {
//顶点集合
List<Integer> vNewList = new ArrayList();
//边集合,int[]{顶点1,顶点2,边长}
List<String[]> eNewList = new ArrayList<>();
//随机起始顶点
// int start = new Random().nextInt(matrix.length);
int start = 0;
vNewList.add(start);
int sum = 0;
while (vNewList.size() != matrix.length) {
//最小边
int minSide = Integer.MAX_VALUE;
//最小边对应的新顶点
int minSideStart = -1;
int minSideEnd = -1;
//从已有点出发,找下一个最近的点
for (Integer v : vNewList) {
for (int j = 0; j < matrix.length; j++) {
if (!vNewList.contains(j)) {
//假设边为0是不连通的边,这取决于图是怎么设置的,也可以用Integer.MAX_VALUE
if (matrix[v][j] != 0 && matrix[v][j] < minSide) {
minSide = matrix[v][j];
minSideStart = v;
minSideEnd = j;
}
}
}
}
vNewList.add(minSideEnd);
eNewList.add(new String[]{minSideStart + "", minSideEnd + "", minSide + ""});
sum += minSide;
}
for (String[] strings : eNewList) {
System.out.println("起点" + strings[0] + ",终点" + strings[1] + ",边长" + strings[2]);
}
System.out.println("最小生成树总权值" + sum);
}