1 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。你可以按任意顺序返回答案。
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
思路:
1 创建一个HashMap集合,key待变数组的值,value代表数组的索引,利用map.containsKey()方法进行判断,判断map集合中是否存在与nums[i]互补的值。
2 对数组进行遍历,
map.containsKey(target-nums[i]) map中是否存在 target-nums[i]的key
if(存在){
就得到答案
}else if(不存在){
就把nums[i]放进map的key中,i放在value中
}
import java.util.Map;
import java.util.HashMap;
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer ,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(target-nums[i])){
return new int[]{
map.get(target-nums[i]),i};
}
map.put(nums[i],i);
}
return new int[]{
-1,-1};
}
}
1299 给你一个数组 arr ,请你将每个元素用它右边最大的元素替换,如果是最后一个元素,用 -1 替换。完成所有替换操作后,请你返回这个数组。
示例
输入:arr = [17,18,5,4,6,1]
输出:[18,6,6,6,1,-1]
思路:
1 对数组从右向左进行遍历
for(){
2 用一个辅助变量保存当前元素的值
3 当前的arr[i]=右侧最大值
4 计算下次循环的当前最大值
}
class Solution {
public int[] replaceElements(int[] arr) {
int max=arr[arr.length-1];
int temp=0;
for(int i=arr.length-1;i>=0;i--){
//将用一个辅助节点取值
temp=arr[i];
//将本节点变成右侧最大值
arr[i]=max;
//计算右侧最大值包括自身
max=(temp>=arr[i])?temp:arr[i];
}
arr[arr.length-1]=-1;
return arr;
}
}
1464 给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j,使 (nums[i]-1)*(nums[j]-1) 取得最大值。请你计算并返回该式的最大值。
输入:nums = [3,4,5,2]
输出:12
解释:如果选择下标 i=1 和 j=2(下标从 0 开始),则可以获得最大值,(nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12
思路:
1 利用hashmap去重,把nums[i]保存在key中,index保存在value中
2 定义一个二维数组result保存结果
3 遍历hashmap找到最大值,把nums和i保存在result中
4 移除最大值
5 再次遍历map找到最大值,把nums和i保存在result中
6 根据result中的结果进行计算
class Solution {
public int maxProduct(int[] nums) {
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
//先利用hashmap去重,key保存nums[i]-1,value保存索引
for(int i=0;i<nums.length-1;i++){
map.put(nums[i],i);
}
//定义一个结果二维数组,保存结果
int[][] result=new int[2][2];
//进行第一次遍历,获取最大值
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
if(entry.getKey()>=result[0][0]){
result[0][0]=entry.getKey();
result[0][1]=entry.getValue();
}
}
//移除最大值
map.remove(result[0][0]);
//进行第二次遍历
for(Map.Entry<Integer,Integer> entry:map.entrySet()){
if(entry.getKey()>=result[1][0]){
result[1][0]=entry.getKey();
result[1][1]=entry.getValue();
}
}
//根据result计算
return (nums[result[0][1]]-1)*(nums[result[1][1]]-1);
}
}
15 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。 注意:答案中不可以包含重复的三元组。
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
思路1 :暴力排序 会超时
1 先将nums进行排序
2 三层for循环
for(int i=0;i<=nums.length-3;i++){
for(int j=i+1;j<=nums.length-2;j++){
for(int k=j+1;k<=nums.length-1;k++){
3 判断nums[i]+nums[j]+nums[k]==0
4 存在 放入list集合
5 把list集合放入HashSet集合中去重
}
}
}
6 把Set集合装换成List<List>集合
思路2 :排序+双指针
1 先进行排序
2 用三个指针遍历数组,k 、 i=k+1 、 j=nums.length-1
3 k不动,i、j向中间移动
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Set<List> result=new HashSet<List>();
Arrays.sort(nums);
for(int i=0;i<=nums.length-3;i++){
for(int j=i+1;j<=nums.length-2;j++){
for(int k=j+1;k<=nums.length-1;k++){
List res=new ArrayList();
if(nums[i]+nums[j]+nums[k]==0){
res.add(nums[i]);
res.add(nums[j]);
res.add(nums[k]);
result.add(res);
}
}
}
}
List<List<Integer>> res1=new ArrayList<List<Integer>>();
for(List i:result){
res1.add(i);
}
return res1;
}
}
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
//1 先进行排序
Arrays.sort(nums);
//2 创建list集合返回结果
List<List<Integer>> result=new ArrayList<>();
//3 对数组进行遍历
for(int k=0;k<nums.length-2;k++){
//4 如果nums[k]>0,则三数之和必大于0
if(nums[k]>0){
break;
}
//5 如果nums[k]==nums[k-1] 则跳过这层循环
if(k>0&&nums[k]==nums[k-1]){
continue;
}
//6 初始化i、j指针
int i=k+1,j=nums.length-1;
//7 移动i、j指针
while(i<j){
//8 求和
int sum=nums[k]+nums[i]+nums[j];
//9 sum<0 ,i向右移动 i++
if(sum<0){
//10 移动i,并且去重,直到nums[i]!=nums[++i];
while(i<j&&nums[i]==nums[++i]);//去重
} else if(sum>0){
//11 移动j,并去重
while(i<j&&nums[j]==nums[--j]);
}else if(sum==0){
//12 添加结果
result.add(new ArrayList<Integer>(Arrays.asList(nums[k],nums[i],nums[j])));
//13 i、j同时向中间移动
while(i < j && nums[i] == nums[++i]);
while(i < j && nums[j] == nums[j-1]){
j=j-1;
}
}
}
}
// 14 返回结果
return result;
}
}
给定一组非负整数 nums,重新排列它们每个数字的顺序(每个数字不可拆分)使之组成一个最大的整数。注意:输出结果可能非常大,所以你需要返回一个字符串而不是整数。
输入:nums = [10,2]
输出:"210"
思路:
1 重写排序 实现Comparator接口
2 重写接口方法
3 Arrays.sort(asStrs, new LargerNumberComparator());匿名内部类
进行排序
4 字符串拼接
class Solution {
private class LargerNumberComparator implements Comparator<String> {
//降序排列
@Override
public int compare(String a, String b) {
String order1 = a + b;//如果ab>ba -1 降序 排序结果 ab
String order2 = b + a;//如果ba>ab 1 升序 排序结果 ba
return order2.compareTo(order1);
}
}
public String largestNumber(int[] nums) {
// Get input integers as strings.
String[] asStrs = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
asStrs[i] = String.valueOf(nums[i]);
}
// Sort strings according to custom comparator.
Arrays.sort(asStrs, new LargerNumberComparator());
// If, after being sorted, the largest number is `0`, the entire number
// is zero.
if (asStrs[0].equals("0")) {
return "0";
}
// Build largest number from sorted array.
String largestNumberStr = new String();
for (String numAsStr : asStrs) {
largestNumberStr += numAsStr;
}
return largestNumberStr;
}
}
189 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。
进阶
尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。你可以使用空间复杂度为O(1)的原地算法解决这个问题吗?
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
思路一: 使用额外数组
1创建一个等长的数组
2把元素放在正确的位置上 i+k%n
3用 System.arraycopy() 方法复制过来
class Solution {
public void rotate(int[] nums, int k) {
int n = nums.length;
int[] newArr = new int[n];
for (int i = 0; i < n; ++i) {
newArr[(i + k) % n] = nums[i];
}
//其中:src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度
System.arraycopy(newArr, 0, nums, 0, n);//把newArr中的元素复制道nums中
}
}
思路二:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E1u5Azay-1618453474281)(D:\学习笔记\刷题笔记截图\image-20210414104901897.png)]
class Solution {
//根据k进行翻转
public void rotate(int[] nums, int k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
//编写翻转数组方法
public void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start += 1;
end -= 1;
}
}
}
215 在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
思路1:
1 调用Arrays.sort()先对数组排序 默认升序
2 返回倒数第k个元素
另一种思路:
1 进行k次循环,每次把最大值置为最小值 Integer.MIN_VALUE
2 第k次循环 返回最大值
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
if(k>nums.length){
return 0;
}else{
return nums[nums.length-k];
}
}
}
思路二: 没看明白
堆排序
class Solution {
public int findKthLargest(int[] nums, int k) {
int heapSize = nums.length;
buildMaxHeap(nums, heapSize);
for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
swap(nums, 0, i);
--heapSize;
maxHeapify(nums, 0, heapSize);
}
return nums[0];
}
public void buildMaxHeap(int[] a, int heapSize) {
for (int i = heapSize / 2; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
}
//大顶堆函数
public void maxHeapify(int[] a, int i, int heapSize) {
int l = i * 2 + 1, r = i * 2 + 2, largest = i;
if (l < heapSize && a[l] > a[largest]) {
largest = l;
}
if (r < heapSize && a[r] > a[largest]) {
largest = r;
}
if (largest != i) {
swap(a, i, largest);
maxHeapify(a, largest, heapSize);
}
}
//定义一个交换函数
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
思路三:
快速排序
239 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
思路1: 该思路超时不可行
1 滑动次数nums.length-k+1
2 进行nums.length-k+1遍历,每次遍历代表一次滑动
3 将每次滑动要比较的数放在一个数组中,//对数组进行排序(可用多种排序)//或者取最大值
4 取出最大值依次放在result数组中
排序法
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//定义结果
int[] result=new int[nums.length-k+1];
//滑动次数为nums.length-k+1
int num=nums.length-k+1;
//进行nums.length-k+1遍历 每遍历一次取一次最大值
for(int i=0;i<num;i++){
int[] temp1=new int[k];
//第i次滑动需要比较的数组
for(int j=0;j<temp1.length;j++){
temp1[j]=nums[i+j];
}
result[i]=max(temp1);
}
return result;
}
//定义求最大值函数
public int max(int[] arr){
Arrays.sort(arr);
int a=arr.length;
return arr[a-1];
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//定义结果
int[] result=new int[nums.length-k+1];
//滑动次数为nums.length-k+1
int num=nums.length-k+1;
//进行nums.length-k+1遍历 每遍历一次取一次最大值
for(int i=0;i<num;i++){
int[] temp1=new int[k];
//第i次滑动需要比较的数组
int aa=Integer.MIN_VALUE;
for(int j=0;j<temp1.length;j++){
if(aa<nums[i+j]){
aa=nums[i+j];
}
}
result[i]=aa;
}
return result;
}
}
思路2:
对于两个相邻(只差了一个位置)的滑动窗口,它们共用着 k-1个元素,而只有1个元素是变化的。我们可以根据这个特点进行优化。
使用优先队列
1 创建优先队列,自定义排序顺序,队列中放值的元素为 int[](数组的值和索引)
2 排序规则 先比较数组值降序排列,如果相等索引降序排列
3 队列顶即最大值,即结果
4 每滑动一次,就加进来一个数,同时判断顶值索引是否在新窗口内,在就不用管,不在就要删除
5 再次返回顶值 放在结果集中
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//求出数组长度
int n = nums.length;
//创建优先队列,自定义比较方式
PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] pair1, int[] pair2) {
return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
}
});
//往队列中添加元素
for (int i = 0; i < k; ++i) {
pq.offer(new int[]{
nums[i], i});
}
//定义一个n-k+1的数组 用于存放结果
int[] ans = new int[n - k + 1];
//获取队列头
ans[0] = pq.peek()[0];
//遍历
for (int i = k; i < n; ++i) {
//添加新元素进去
pq.offer(new int[]{
nums[i], i});
//如果上次最大值索引不在这次滑动窗口内,就移除
while (pq.peek()[1] <= i - k) {
//删除队列头
pq.poll();
}
//把本次结果放在结果数组中
ans[i - k + 1] = pq.peek()[0];
}
//返回结果
return ans;
}
}
33 整数数组 nums 按升序排列,数组中的值互不相同 。
在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。
给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的索引,否则返回 -1
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1
思路: 双向遍历
1 先根据数组大小 确定使用什么遍历方式
2 数组小 使用普通 数组大 使用两端
3 根据target的值和nums[i]的比较确定从哪头进行比较
class Solution {
public int search(int[] nums, int target) {
//1如果数组长度小于100,则普通遍历即可
if(nums.length<100){
for(int i=0;i<nums.length;i++){
if(nums[i]==target){
return i;
}
}
return -1;
}else{
//2如果数组长度大于100
//3判断从那边遍历
if(target>=nums[0]){
//4从左边
//先比较第一个数
if(nums[0]==target){
return 0;
}
int i=1;
//从第二个数开始遍历,直到翻转的位置
while(nums[i]<target&& i<nums.length && nums[i]>nums[i-1]){
i++;
}
//如果nums[i]=target 则找到,不等于则没有找到
if(nums[i]!=target){
return -1;
}else{
return i;
}
}else{
//从右边
if(nums[nums.length-1]==target){
return nums.length-1;
}
int i=nums.length-2;
while(nums[i]>target&& i>=0 &&nums[i]<nums[i+1]){
i--;
}
if(nums[i]!=target){
return -1;
}else{
return i;
}
}
}
}
}
思路2 :搜索旋转数组
class Solution {
public int search(int[] nums, int target) {
int n = nums.length;
if (n == 0) {
return -1;
}
if (n == 1) {
return nums[0] == target ? 0 : -1;
}
int l = 0, r = n - 1;
while (l <= r) {
int mid = (l + r) / 2;
if (nums[mid] == target) {
return mid;
}
if (nums[0] <= nums[mid]) {
if (nums[0] <= target && target < nums[mid]) {
r = mid - 1;
} else {
l = mid + 1;
}
} else {
if (nums[mid] < target && target <= nums[n - 1]) {
l = mid + 1;
} else {
r = mid - 1;
}
}
}
return -1;
}
}
思路3:
1 找到中间点
2 分成两个有序数组
3 遍历数组
349 给定两个数组,编写一个函数来计算它们的交集。
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
说明:
输出结果中的每个元素一定是唯一的。
我们可以不考虑输出结果的顺序。
思路:
1 将两个数组放到hashset中去重
2 用小数组 遍历大数组 用contains方法判断是否有交集
3 将交集放在hashset中去重
4 将hashset转换成int数组
5 返回结果
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
Set<Integer> set1 = new HashSet<Integer>();
Set<Integer> set2 = new HashSet<Integer>();
for (int num : nums1) {
set1.add(num);
}
for (int num : nums2) {
set2.add(num);
}
return getIntersection(set1, set2);
}
public int[] getIntersection(Set<Integer> set1, Set<Integer> set2) {
//小数组驱动大数组
if (set1.size() > set2.size()) {
return getIntersection(set2, set1);
}
Set<Integer> intersectionSet = new HashSet<Integer>();
for (int num : set1) {
if (set2.contains(num)) {
intersectionSet.add(num);
}
}
int[] intersection = new int[intersectionSet.size()];
int index = 0;
for (int num : intersectionSet) {
intersection[index++] = num;
}
return intersection;
}
}
350 给定两个数组,编写一个函数来计算它们的交集。
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2,2]
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[4,9]
说明:
输出结果中每个元素出现的次数,应与元素在两个数组中出现次数的最小值一致。
我们可以不考虑输出结果的顺序。
方法一:哈希表
由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。
首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。
为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
//小数组驱动大数组
if (nums1.length > nums2.length) {
return intersect(nums2, nums1);
}
//创建map集合 第一个数保存值 第二个数保存次数
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//将num1处理后保存在map中
for (int num : nums1) {
int count = map.getOrDefault(num, 0) + 1;
map.put(num, count);
}
//定义一个数组
int[] intersection = new int[nums1.length];
int index = 0;
for (int num : nums2) {
int count = map.getOrDefault(num, 0); //返回 指定键所包含的映射值 或者 defaultValue
//如果map中的个数>0 说明是交集
if (count > 0) {
//把该元素放在数组中
intersection[index++] = num;
//同时 map中的数量-1
count--;
//如果map中该元素的数量还大于0 就更新数据
if (count > 0) {
map.put(num, count);
//否则 移除该数据
} else {
map.remove(num);
}
}
}
//返回结果
return Arrays.copyOfRange(intersection, 0, index);
}
}
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
class Solution {
public int findRepeatNumber(int[] nums) {
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<nums.length;i++){
if(map.containsKey(nums[i])){
return nums[i];
}else{
map.put(nums[i],1);
}
}
return 0;
}
}
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int i = matrix.length - 1, j = 0;
while(i >= 0 && j < matrix[0].length)
{
if(matrix[i][j] > target) i--;
else if(matrix[i][j] < target) j++;
else return true;
}
return false;
}
}
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
class Solution {
public int minArray(int[] numbers) {
if(numbers.length==0) return 0;
if(numbers.length==1) return numbers[0];
int length=numbers.length;
if(numbers[0]>=numbers[length-1]){
for(int i=1;i<numbers.length;i++){
if(numbers[i]<numbers[i-1]){
return numbers[i];
}
}
}
return numbers[0];
}
}
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)
[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]
但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
示例 2:
输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false
提示:
1 <= board.length <= 200
1 <= board[i].length <= 200
class Solution {
public boolean exist(char[][] board, String word) {
char[] words = word.toCharArray();
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
if(dfs(board, words, i, j, 0)) return true;
}
}
return false;
}
boolean dfs(char[][] board, char[] word, int i, int j, int k) {
if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != word[k]) return false;
if(k == word.length - 1) return true;
board[i][j] = '\0'; //防止回溯回去
boolean res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) ||
dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
board[i][j] = word[k];
return res;
}
}
1312 给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。
请你返回让 s 成为回文串的 最少操作次数 。「回文串」是正读和反读都相同的字符串。
输入:s = "zzazz"
输出:0
解释:字符串 "zzazz" 已经是回文串了,所以不需要做任何插入操作。
输入:s = "mbadm"
输出:2
解释:字符串可变为 "mbdadbm" 或者 "mdbabdm"
class Solution {
private class LargerNumberComparator implements Comparator<String> {
//降序排列
@Override
public int compare(String a, String b) {
String order1 = a + b;
String order2 = b + a;
return order2.compareTo(order1);
}
}
public String largestNumber(int[] nums) {
// Get input integers as strings.
String[] asStrs = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
asStrs[i] = String.valueOf(nums[i]);
}
// Sort strings according to custom comparator.
Arrays.sort(asStrs, new LargerNumberComparator());
// If, after being sorted, the largest number is `0`, the entire number
// is zero.
if (asStrs[0].equals("0")) {
return "0";
}
// Build largest number from sorted array.
String largestNumberStr = new String();
for (String numAsStr : asStrs) {
largestNumberStr += numAsStr;
}
return largestNumberStr;
}
}
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
示例 1:
输入:s = "We are happy."
输出:"We%20are%20happy."
限制:
0 <= s 的长度 <= 10000
class Solution {
public String replaceSpace(String s) {
StringBuffer bf=new StringBuffer();
for(char c:s.toCharArray()){
if(c==' '){
bf.append("%20");
}else{
bf.append(c);
}
}
return bf.toString();
}
}
思想:
大顶堆
依次遍历,从第一个非叶子节点向根节点遍历
和自己的左右子节点比较,把数值大的放在根节点,数值小的放在子节点
依次进行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c4o0dPyI-1618453474284)(D:\学习笔记\刷题笔记截图\image-20210414110809721.png)]
claas Solution{
public void buildMaxHeap(int[] a, int heapSize) {
for (int i = heapSize / 2; i >= 0; --i) {
maxHeapify(a, i, heapSize);
}
}
//大顶堆函数
public void maxHeapify(int[] a, int i, int heapSize) {
int l = i * 2 + 1, r = i * 2 + 2, largest = i;
if (l < heapSize && a[l] > a[largest]) {
largest = l;
}
if (r < heapSize && a[r] > a[largest]) {
largest = r;
}
if (largest != i) {
swap(a, i, largest);
maxHeapify(a, largest, heapSize);
}
}
//定义一个交换函数
public void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
思想:
冒泡排序的升级
先取一个中间值,用两个指针,依次向中间遍历
分别找到小于中间值的数和大于中间值的数,交换,把数组分成两个部分
再次对两个数组排序
依次进行
import java.util.Arrays;
public class QuickSort {
public static void quickSort(int[] arr, int left, int right) {
int l = left; //左下标
int r = right; //右下标
//pivot 中轴值
int pivot = arr[(left + right) / 2];
int temp = 0; //临时变量,作为交换时使用
//while 循环的目的是让比 pivot 值小放到左边
//比 pivot 值大放到右边
while (l < r) {
//在 pivot 的左边一直找,找到大于等于 pivot 值,才退出
while (arr[l] < pivot) {
l += 1;
}
//在 pivot 的右边一直找,找到小于等于 pivot 值,才退出
while (arr[r] > pivot) {
r -= 1;
}
//如果 l >= r 说明 pivot 的左右两的值,已经按照左边全部是
//小于等于 pivot 值,右边全部是大于等于 pivot 值
if (l >= r) {
break;
}
//交换
temp = arr[l];
arr[l] = arr[r];
arr[r] = temp;
//如果交换完后,发现这个 arr[l] == pivot 值 相等 r--, 前移
if (arr[l] == pivot) {
r -= 1;
}
//如果交换完后,发现这个 arr[r] == pivot 值 相等 l++, 后移
if (arr[r] == pivot) {
l += 1;
}
}
// 如果 l == r, 必须 l++, r--, 否则为出现栈溢出
if (l == r) {
l += 1;
r -= 1;
}
//向左递归
if (left < r) {
quickSort(arr, left, r);
}
//向右递归
if (right > l) {
quickSort(arr, l, right);
}
}
}
思想:
假设第一个数是一个有序数组
遍历数组,通过比较插入数据和有序数组的大小,找到插入位置
赋值,完成排序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ReYKKTMI-1618453474286)(D:\学习笔记\刷题笔记截图\image-20210414110834412.png)]
public static void insertSort(int[] arr) {
//从第一个需要插入的位置开始 默认左边第一个元素是一个有序数组
for(int i = 1; i < arr.length; ++i) {
//先用insertVal保存要插入的数据
int insertVal = arr[i];
//定义插入的位置
int insertIndex;
//起始位置为0 该循环从右向左遍历,如果插入的数比插入索引位置的数小,向右移,直到找到索引位置
//当要插入的数大于等于索引位置的元素 结束循环
//此时 要插入的位置是 index+1
for(insertIndex = i - 1; insertIndex >= 0 && insertVal < arr[insertIndex]; --insertIndex) {
arr[insertIndex + 1] = arr[insertIndex];
}
//判断 index+1 是否等于 i
//等于表示这个数就是右序数组最大值,无需变动
//不等于表示,需要进行赋值
if (insertIndex + 1 != i) {
arr[insertIndex + 1] = insertVal;
}
}
}
1 先分组,遍历
2 对每个组进行插入排序
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6GDpWS5B-1618453474287)(D:\学习笔记\刷题笔记截图\image-20210414105105633.png)]
//1 交换式
public static void shellSort(int[] arr) {
int temp = 0;
int count = 0;
// 根据前面的逐步分析,使用循环处理
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
for (int i = gap; i < arr.length; i++) {
// 遍历各组中所有的元素(共 gap 组,每组有个元素), 步长 gap
for (int j = i - gap; j >= 0; j -= gap) {
// 如果当前元素大于加上步长后的那个元素,说明交换
if (arr[j] > arr[j + gap]) {
temp = arr[j];
arr[j] = arr[j + gap];
arr[j + gap] = temp;
}
}
}
}
}
//2 移动式
public static void shellSort2(int[] arr) {
// 增量 gap, 并逐步的缩小增量
for (int gap = arr.length / 2; gap > 0; gap /= 2) {
// 从第 gap 个元素,逐个对其所在的组进行直接插入排序
//接下来是插入排序
for (int i = gap; i < arr.length; i++) {
int insertVal=arr[i];
int insertIndex=0;
for(insertIndex=i-gap;insertIndex>=0 && insertVal<arr[insertIndex];insertIndex--){
arr[insertIndex+gap]=arr[insertIndex];
}
if(insertIndex+1!=i){
arr[insertIndex+1]=insertVal;
}
}
}
}
进行n-1次遍历
遍历的时候碰见逆序就交换
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NfdagJwL-1618453474288)(D:\学习笔记\刷题笔记截图\image-20210414105130186.png)]
// 将前面额冒泡排序算法,封装成一个方法
public static void bubbleSort(int[] arr) {
// 冒泡排序 的时间复杂度 O(n^2), 自己写出
int temp = 0; // 临时变量
boolean flag = false; // 标识变量,表示是否进行过交换
for (int i = 0; i < arr.length - 1; i++) {
//表示第几趟排序,每一趟排好一个元素
for (int j = 0; j < arr.length - 1 - i; j++) {
//每次都从0开始
// 如果前面的数比后面的数大,则交换
if (arr[j] > arr[j + 1]) {
flag = true;
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
if (!flag) {
// 在一趟排序中,一次交换都没有发生过
break;
} else {
flag = false; // 重置 flag!!!, 进行下次判断
}
}
}
思路:
第一次 从arr[0]-arr[n-1]找到最小的数与arr[0]互换
第二次 从arr[1]-arr[n-1]找到最小的数与arr[1]互换
.....
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XrzAuMpL-1618453474288)(D:\学习笔记\刷题笔记截图\image-20210414105152861.png)]
public static void selectSort(int[] arr) {
//选择排序时间复杂度是 O(n^2)
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;
int min = arr[i];
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) {
// 说明假定的最小值,并不是最小
min = arr[j]; // 重置 min
minIndex = j; // 重置 minIndex
}
}
if (minIndex != i) {
arr[minIndex] = arr[i];
arr[i] = min;
}
}
思想
1 先进行数组拆分
2 再依次合并
第一个 分
1 从中间分开,左边右两边都进行分割
2 分割后进行合并 此处用了递归
第二个 合
1 用两个指针遍历两个数组,依次比较,按照顺序把元素放到temp数组中
2 把剩余的元素移动道temp数组中
3 把temp数组复制到原来的数组中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMVfJDoe-1618453474289)(D:\学习笔记\刷题笔记截图\image-20210414105218123.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-StiKp98t-1618453474289)(D:\学习笔记\刷题笔记截图\image-20210414105244756.png)]
public static void mergeSort(int[] arr, int left, int right, int[] temp) {
if (left < right) {
int mid = (left + right) / 2; //中间索引
//向左递归进行分解
mergeSort(arr, left, mid, temp);
//向右递归进行分解
mergeSort(arr, mid + 1, right, temp);
//合并
merge(arr, left, mid, right, temp);
}
}
//合并算法
public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
int i = left; // 初始化 i, 左边有序序列的初始索引
int j = mid + 1; //初始化 j, 右边有序序列的初始索引
int t = 0; // 指向 temp 数组的当前索引
//(一)
//先把左右两边(有序)的数据按照规则填充到 temp 数组
//直到左右两边的有序序列,有一边处理完毕为止
while (i <= mid && j <= right) {
//继续
//如果左边的有序序列的当前元素,小于等于右边有序序列的当前元素
//即将左边的当前元素,填充到 temp 数组
//然后 t++, i++
if (arr[i] <= arr[j]) {
temp[t] = arr[i];
t += 1;
i += 1;
} else {
//反之,将右边有序序列的当前元素,填充到 temp 数组
temp[t] = arr[j];
t += 1;
j += 1;
}
}
//(二)
//把有剩余数据的一边的数据依次全部填充到 temp
while (i <= mid) {
//左边的有序序列还有剩余的元素,就全部填充到 temp
temp[t] = arr[i];
t += 1;
i += 1;
}
while (j <= right) {
//右边的有序序列还有剩余的元素,就全部填充到 temp
temp[t] = arr[j];
t += 1;
j += 1;
}
//(三)
//将 temp 数组的元素拷贝到 arr
//注意,并不是每次都拷贝所有
t = 0;
int tempLeft = left; //
//第一次合并 tempLeft = 0 , right = 1 // tempLeft = 2 right = 3 // tL=0 ri=3
//最后一次 tempLeft = 0 right = 7
while (tempLeft <= right) {
arr[tempLeft] = temp[t];
t += 1;
tempLeft += 1;
}
}
//基数排序方法
public static void radixSort(int[] arr) {
//根据前面的推导过程,我们可以得到最终的基数排序代码
//1. 得到数组中最大的数的位数
int max = arr[0]; //假设第一数就是最大数
for (int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//得到最大数是几位数
int maxLength = (max + "").length();
//定义一个二维数组,表示 10 个桶, 每个桶就是一个一维数组
//说明
//1. 二维数组包含 10 个一维数组
//2. 为了防止在放入数的时候,数据溢出,则每个一维数组(桶),大小定为 arr.length
//3. 名明确,基数排序是使用空间换时间的经典算法
int[][] bucket = new int[10][arr.length];
//为了记录每个桶中,实际存放了多少个数据,我们定义一个一维数组来记录各个桶的每次放入的数据个数
//可以这里理解
//比如:bucketElementCounts[0] , 记录的就是 bucket[0] 桶的放入数据个数
int[] bucketElementCounts = new int[10];
//这里我们使用循环将代码处理
for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
//(针对每个元素的对应位进行排序处理), 第一次是个位,第二次是十位,第三次是百位..
for (int j = 0; j < arr.length; j++) {
//取出每个元素的对应位的值
int digitOfElement = arr[j] / n % 10;
//放入到对应的桶中
bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
bucketElementCounts[digitOfElement]++;
}
//按照这个桶的顺序(一维数组的下标依次取出数据,放入原来数组)
int index = 0;
//遍历每一桶,并将桶中是数据,放入到原数组
for (int k = 0; k < bucketElementCounts.length; k++) {
//如果桶中,有数据,我们才放入到原数组
if (bucketElementCounts[k] != 0) {
//循环该桶即第 k 个桶(即第 k 个一维数组), 放入
for (int l = 0; l < bucketElementCounts[k]; l++) {
//取出元素放入到 arr
arr[index++] = bucket[k][l];
}
}
//第 i+1 轮处理后,需要将每个 bucketElementCounts[k] = 0 !!!!
bucketElementCounts[k] = 0;
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8MezzPOM-1618453474290)(D:\学习笔记\刷题笔记截图\image-20210414105344519.png)]
思路:
class BinarySortTree{
Node root=new Node();
public Node getRoot(){
return root;
}
1 添加方法
如果root为空,root=node
root不为空 root.add(node)
2 中序遍历
root为空 return
root 不为空 root.infixOrder()
}
class Node{
1 中序遍历方法
先根节点--左子节点--右子节点
2 添加节点方法
如果要添加的节点为空 return
如果要添加的节点比本节点小 向左侧添加(需考虑子节点为空的情况)
如果要添加的节点比本节点大 向右侧添加
3 查找节点
如果本节点=value return this
如果本节点<value
3.1 本节点左子节点为null 找不到
3.2 本节点左子节点不为空 return this.left.search(value)
如果本节点>value
3.1 本节点右子节点为null 找不到
3.2 本节点右子节点不为空 return this.right.search(value)
4 查找父节点
如果左子节点不为空&&左子节点的值=value || 右子节点不为空&&右子节点的值=value return this
else{
value<this.value &&this.value!=null return this.left.searchParent(value) //向左递归
value>this.value &&this.value!=null return this.right.searchParent(value) //向右递归
否则 return null
}
}
}
package com.atguigu.binarysorttree;
public class BinarySortTreeDemo {
public static void main(String[] args) {
int[] arr = {
7, 3, 10, 12, 5, 1, 9, 2};
BinarySortTree binarySortTree = new BinarySortTree();
//循环的添加结点到二叉排序树
for(int i = 0; i< arr.length; i++) {
binarySortTree.add(new Node(arr[i]));
}
//中序遍历二叉排序树
System.out.println("中序遍历二叉排序树~");
binarySortTree.infixOrder(); // 1, 3, 5, 7, 9, 10, 12
//测试一下删除叶子结点
binarySortTree.delNode(12);
binarySortTree.delNode(5);
binarySortTree.delNode(10);
binarySortTree.delNode(2);
binarySortTree.delNode(3);
binarySortTree.delNode(9);
binarySortTree.delNode(1);
binarySortTree.delNode(7);
System.out.println("root=" + binarySortTree.getRoot());
System.out.println("删除结点后");
binarySortTree.infixOrder();
}
}
//创建二叉排序树
class BinarySortTree {
private Node root;
public Node getRoot() {
return root;
}
//查找要删除的结点
public Node search(int value) {
if(root == null) {
return null;
} else {
return root.search(value);
}
}
//查找父结点
public Node searchParent(int value) {
if(root == null) {
return null;
} else {
return root.searchParent(value);
}
}
//编写方法:
//1. 返回的 以node 为根结点的二叉排序树的最小结点的值
//2. 删除node 为根结点的二叉排序树的最小结点
/**
*
* @param node 传入的结点(当做二叉排序树的根结点)
* @return 返回的 以node 为根结点的二叉排序树的最小结点的值
*/
public int delRightTreeMin(Node node) {
Node target = node;
//循环的查找左子节点,就会找到最小值
while(target.left != null) {
target = target.left;
}
//这时 target就指向了最小结点
//删除最小结点
delNode(target.value);
return target.value;
}
//删除结点
public void delNode(int value) {
if(root == null) {
return;
}else {
//1.需求先去找到要删除的结点 targetNode
Node targetNode = search(value);
//如果没有找到要删除的结点
if(targetNode == null) {
return;
}
//如果我们发现当前这颗二叉排序树只有一个结点
if(root.left == null && root.right == null) {
root = null;
return;
}
//去找到targetNode的父结点
Node parent = searchParent(value);
//如果要删除的结点是叶子结点
if(targetNode.left == null && targetNode.right == null) {
//判断targetNode 是父结点的左子结点,还是右子结点
if(parent.left != null && parent.left.value == value) {
//是左子结点
parent.left = null;
} else if (parent.right != null && parent.right.value == value) {
//是由子结点
parent.right = null;
}
} else if (targetNode.left != null && targetNode.right != null) {
//删除有两颗子树的节点
int minVal = delRightTreeMin(targetNode.right);
targetNode.value = minVal;
} else {
// 删除只有一颗子树的结点
//如果要删除的结点有左子结点
if(targetNode.left != null) {
if(parent != null) {
//如果 targetNode 是 parent 的左子结点
if(parent.left.value == value) {
parent.left = targetNode.left;
} else {
// targetNode 是 parent 的右子结点
parent.right = targetNode.left;
}
} else {
root = targetNode.left;
}
} else {
//如果要删除的结点有右子结点
if(parent != null) {
//如果 targetNode 是 parent 的左子结点
if(parent.left.value == value) {
parent.left = targetNode.right;
} else {
//如果 targetNode 是 parent 的右子结点
parent.right = targetNode.right;
}
} else {
root = targetNode.right;
}
}
}
}
}
//添加结点的方法
public void add(Node node) {
if(root == null) {
root = node;//如果root为空则直接让root指向node
} else {
root.add(node);
}
}
//中序遍历
public void infixOrder() {
if(root != null) {
root.infixOrder();
} else {
System.out.println("二叉排序树为空,不能遍历");
}
}
}
//创建Node结点
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
//查找要删除的结点
/**
*
* @param value 希望删除的结点的值
* @return 如果找到返回该结点,否则返回null
*/
public Node search(int value) {
if(value == this.value) {
//找到就是该结点
return this;
} else if(value < this.value) {
//如果查找的值小于当前结点,向左子树递归查找
//如果左子结点为空
if(this.left == null) {
return null;
}
return this.left.search(value);
} else {
//如果查找的值不小于当前结点,向右子树递归查找
if(this.right == null) {
return null;
}
return this.right.search(value);
}
}
//查找要删除结点的父结点
/**
*
* @param value 要找到的结点的值
* @return 返回的是要删除的结点的父结点,如果没有就返回null
*/
public Node searchParent(int value) {
//如果当前结点就是要删除的结点的父结点,就返回
if((this.left != null && this.left.value == value) ||
(this.right != null && this.right.value == value)) {
return this;
} else {
//如果查找的值小于当前结点的值, 并且当前结点的左子结点不为空
if(value < this.value && this.left != null) {
return this.left.searchParent(value); //向左子树递归查找
} else if (value >= this.value && this.right != null) {
return this.right.searchParent(value); //向右子树递归查找
} else {
return null; // 没有找到父结点
}
}
}
@Override
public String toString() {
return "Node [value=" + value + "]";
}
//添加结点的方法
//递归的形式添加结点,注意需要满足二叉排序树的要求
public void add(Node node) {
if(node == null) {
return;
}
//判断传入的结点的值,和当前子树的根结点的值关系
if(node.value < this.value) {
//如果当前结点左子结点为null
if(this.left == null) {
this.left = node;
} else {
//递归的向左子树添加
this.left.add(node);
}
} else {
//添加的结点的值大于 当前结点的值
if(this.right == null) {
this.right = node;
} else {
//递归的向右子树添加
this.right.add(node);
}
}
}
//中序遍历
public void infixOrder() {
if(this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if(this.right != null) {
this.right.infixOrder();
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3fp30BST-1618453474291)(D:\学习笔记\刷题笔记截图\image-20210414105509938.png)]
// 平衡二叉树实现方法:红黑树、AVL、替罪羊树、Treap、伸展树
// 平衡二叉树 左右子树 高度之差不超过1
// 创建AVLTree
class AVLTree {
private Node root;
public Node getRoot() {
return root;
}
// 查找要删除的结点
public Node search(int value) {
if (root == null) {
return null;
} else {
return root.search(value);
}
}
// 查找父结点
public Node searchParent(int value) {
if (root == null) {
return null;
} else {
return root.searchParent(value);
}
}
// 编写方法:
// 1. 返回的 以node 为根结点的二叉排序树的最小结点的值
// 2. 删除node 为根结点的二叉排序树的最小结点
/**
*
* @param node
* 传入的结点(当做二叉排序树的根结点)
* @return 返回的 以node 为根结点的二叉排序树的最小结点的值
*/
public int delRightTreeMin(Node node) {
Node target = node;
// 循环的查找左子节点,就会找到最小值
while (target.left != null) {
target = target.left;
}
// 这时 target就指向了最小结点
// 删除最小结点
delNode(target.value);
return target.value;
}
// 删除结点
public void delNode(int value) {
if (root == null) {
return;
} else {
// 1.需求先去找到要删除的结点 targetNode
Node targetNode = search(value);
// 如果没有找到要删除的结点
if (targetNode == null) {
return;
}
// 如果我们发现当前这颗二叉排序树只有一个结点
if (root.left == null && root.right == null) {
root = null;
return;
}
// 去找到targetNode的父结点
Node parent = searchParent(value);
// 如果要删除的结点是叶子结点
if (targetNode.left == null && targetNode.right == null) {
// 判断targetNode 是父结点的左子结点,还是右子结点
if (parent.left != null && parent.left.value == value) {
// 是左子结点
parent.left = null;
} else if (parent.right != null && parent.right.value == value) {
// 是由子结点
parent.right = null;
}
} else if (targetNode.left != null && targetNode.right != null) {
// 删除有两颗子树的节点
int minVal = delRightTreeMin(targetNode.right);
targetNode.value = minVal;
} else {
// 删除只有一颗子树的结点
// 如果要删除的结点有左子结点
if (targetNode.left != null) {
if (parent != null) {
// 如果 targetNode 是 parent 的左子结点
if (parent.left.value == value) {
parent.left = targetNode.left;
} else {
// targetNode 是 parent 的右子结点
parent.right = targetNode.left;
}
} else {
root = targetNode.left;
}
} else {
// 如果要删除的结点有右子结点
if (parent != null) {
// 如果 targetNode 是 parent 的左子结点
if (parent.left.value == value) {
parent.left = targetNode.right;
} else {
// 如果 targetNode 是 parent 的右子结点
parent.right = targetNode.right;
}
} else {
root = targetNode.right;
}
}
}
}
}
// 添加结点的方法
public void add(Node node) {
if (root == null) {
root = node;// 如果root为空则直接让root指向node
} else {
root.add(node);
}
}
// 中序遍历
public void infixOrder() {
if (root != null) {
root.infixOrder();
} else {
System.out.println("二叉排序树为空,不能遍历");
}
}
}
// 创建Node结点
class Node {
int value;
Node left;
Node right;
public Node(int value) {
this.value = value;
}
// 返回左子树的高度
public int leftHeight() {
if (left == null) {
return 0;
}
return left.height();
}
// 返回右子树的高度
public int rightHeight() {
if (right == null) {
return 0;
}
return right.height();
}
// 返回 以该结点为根结点的树的高度
public int height() {
return Math.max(left == null ? 0 : left.height(), right == null ? 0 : right.height()) + 1;
}
//左旋转方法
private void leftRotate() {
//创建新的结点,以当前根结点的值
Node newNode = new Node(value);
//把新的结点的左子树设置成当前结点的左子树
newNode.left = left;
//把新的结点的右子树设置成带你过去结点的右子树的左子树
newNode.right = right.left;
//把当前结点的值替换成右子结点的值
value = right.value;
//把当前结点的右子树设置成当前结点右子树的右子树
right = right.right;
//把当前结点的左子树(左子结点)设置成新的结点
left = newNode;
}
//右旋转
private void rightRotate() {
Node newNode = new Node(value);
newNode.right = right;
newNode.left = left.right;
value = left.value;
left = left.left;
right = newNode;
}
// 查找要删除的结点
/**
*
* @param value
* 希望删除的结点的值
* @return 如果找到返回该结点,否则返回null
*/
public Node search(int value) {
if (value == this.value) {
// 找到就是该结点
return this;
} else if (value < this.value) {
// 如果查找的值小于当前结点,向左子树递归查找
// 如果左子结点为空
if (this.left == null) {
return null;
}
return this.left.search(value);
} else {
// 如果查找的值不小于当前结点,向右子树递归查找
if (this.right == null) {
return null;
}
return this.right.search(value);
}
}
// 查找要删除结点的父结点
/**
*
* @param value
* 要找到的结点的值
* @return 返回的是要删除的结点的父结点,如果没有就返回null
*/
public Node searchParent(int value) {
// 如果当前结点就是要删除的结点的父结点,就返回
if ((this.left != null && this.left.value == value) || (this.right != null && this.right.value == value)) {
return this;
} else {
// 如果查找的值小于当前结点的值, 并且当前结点的左子结点不为空
if (value < this.value && this.left != null) {
return this.left.searchParent(value); // 向左子树递归查找
} else if (value >= this.value && this.right != null) {
return this.right.searchParent(value); // 向右子树递归查找
} else {
return null; // 没有找到父结点
}
}
}
@Override
public String toString() {
return "Node [value=" + value + "]";
}
// 添加结点的方法
// 递归的形式添加结点,注意需要满足二叉排序树的要求
public void add(Node node) {
if (node == null) {
return;
}
// 判断传入的结点的值,和当前子树的根结点的值关系
if (node.value < this.value) {
// 如果当前结点左子结点为null
if (this.left == null) {
this.left = node;
} else {
// 递归的向左子树添加
this.left.add(node);
}
} else {
// 添加的结点的值大于 当前结点的值
if (this.right == null) {
this.right = node;
} else {
// 递归的向右子树添加
this.right.add(node);
}
}
//当添加完一个结点后,如果: (右子树的高度-左子树的高度) > 1 , 左旋转
if(rightHeight() - leftHeight() > 1) {
//如果它的右子树的左子树的高度大于它的右子树的右子树的高度
if(right != null && right.leftHeight() > right.rightHeight()) {
//先对右子结点进行右旋转
right.rightRotate();
//然后在对当前结点进行左旋转
leftRotate(); //左旋转..
} else {
//直接进行左旋转即可
leftRotate();
}
return ; //必须要!!!
}
//当添加完一个结点后,如果 (左子树的高度 - 右子树的高度) > 1, 右旋转
if(leftHeight() - rightHeight() > 1) {
//如果它的左子树的右子树高度大于它的左子树的高度
if(left != null && left.rightHeight() > left.leftHeight()) {
//先对当前结点的左结点(左子树)->左旋转
left.leftRotate();
//再对当前结点进行右旋转
rightRotate();
} else {
//直接进行右旋转即可
rightRotate();
}
}
}
// 中序遍历
public void infixOrder() {
if (this.left != null) {
this.left.infixOrder();
}
System.out.println(this);
if (this.right != null) {
this.right.infixOrder();
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a9dNFaSA-1618453474292)(D:\学习笔记\刷题笔记截图\image-20210414105542317.png)]
1 所有子节点都在同一层
2 有两个子节点的节点叫二节点,二节点要么没有子节点,要么有两个子节点
3 有三个子节点的节点叫三节点,三节点要么没有子节点,要么有三个子节点
4 2-3树是由二节点和三节点构成的树
5 2-3树在构建过程中依然是2-3树
优点 :降低二叉树高度,提高搜索效率
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-goLSOiRT-1618453474293)(D:\学习笔记\刷题笔记截图\image-20210414105655308.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7og7eRma-1618453474294)(D:\学习笔记\刷题笔记截图\image-20210414105709464.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PSmFTzmb-1618453474294)(D:\学习笔记\刷题笔记截图\image-20210414105724633.png)]
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如,给出
前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树:
3
/ \
9 20
/ \
15 7
限制:
0 <= 节点个数 <= 5000
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
HashMap<Integer, Integer> map = new HashMap<>();//标记中序遍历
int[] preorder;//保留的先序遍历,方便递归时依据索引查看先序遍历的值
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
//将中序遍历的值及索引放在map中,方便递归时获取左子树与右子树的数量及其根的索引
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
//三个索引分别为
//当前根的的索引
//递归树的左边界,即数组左边界
//递归树的右边界,即数组右边界
return recur(0,0,inorder.length-1);
}
TreeNode recur(int pre_root, int in_left, int in_right){
if(in_left > in_right) return null;// 相等的话就是自己
TreeNode root = new TreeNode(preorder[pre_root]);//获取root节点
int idx = map.get(preorder[pre_root]);//获取在中序遍历中根节点所在索引,以方便获取左子树的数量
//左子树的根的索引为先序中的根节点+1
//递归左子树的左边界为原来的中序in_left
//递归右子树的右边界为中序中的根节点索引-1
root.left = recur(pre_root+1, in_left, idx-1);
//右子树的根的索引为先序中的 当前根位置 + 左子树的数量 + 1
//递归右子树的左边界为中序中当前根节点+1
//递归右子树的有边界为中序中原来右子树的边界
root.right = recur(pre_root + (idx - in_left) + 1, idx+1, in_right);
return root;
}
}
剑指 Offer 06. 从尾到头打印链表
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2]
输出:[2,3,1]
限制:
0 <= 链表长度 <= 10000
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] reversePrint(ListNode head) {
List<Integer> list=new ArrayList<Integer>();
ListNode temp=new ListNode();
temp=head;
if(head!=null){
list.add(temp.val);
while(temp.next!=null){
list.add(temp.next.val);
temp=temp.next;
}
}
int[] result=new int[list.size()];
for(int i=list.size()-1,j=0;i>=0;i--,j++){
result[j]=list.get(i);
}
return result;
}
}
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
示例 1:
输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]
示例 2:
输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]
提示:
1 <= values <= 10000
最多会对 appendTail、deleteHead 进行 10000 次调用
class CQueue {
LinkedList<Integer> A, B;
public CQueue() {
A = new LinkedList<Integer>();
B = new LinkedList<Integer>();
}
public void appendTail(int value) {
A.addLast(value);
}
public int deleteHead() {
if(!B.isEmpty()) return B.removeLast();
if(A.isEmpty()) return -1;
while(!A.isEmpty())
B.addLast(A.removeLast());
return B.removeLast();
}
}
public class Offer09 {
Stack<Integer> stack1,stack2;
public Offer09() {
stack1 = new Stack<>(); // 插入
stack2 = new Stack<>(); // 删除
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if (stack2.isEmpty()) {
// 删除栈B为空,则去A栈找数据
if (stack1.isEmpty()) {
// 若A栈也为空,那么无数据可删,则返回-1
return -1;
}
// A栈不为空,先将A栈的值压入B栈,再删除
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:1
示例 2:
输入:n = 5
输出:5
提示:
0 <= n <= 10
//递归法
class Solution {
public int fib(int n) {
if(n<=2){
if(n==2){
return 1;
}else if(n==1){
return 1;
}else if(n==0){
return 0;
}
}
return fib(n-1)+fib(n-2);
}
}
//记忆递归法
//动态规划
class Solution {
public int fib(int n) {
int a = 0, b = 1, sum;
for(int i = 0; i < n; i++){
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return a;
}
}
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2
输出:2
示例 2:
输入:n = 7
输出:21
示例 3:
输入:n = 0
输出:1
提示:
0 <= n <= 100
//和斐波那契数列很类似
class Solution {
public int numWays(int n) {
int a = 1, b = 1, sum;
for(int i = 0; i < n; i++){
sum = (a + b) % 1000000007;
a = b;
b = sum;
}
return a;
}
}
邻接矩阵
邻接表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-icu7aujp-1618453474295)(D:\学习笔记\刷题笔记截图\image-20210414110447526.png)]
import java.util.ArrayList;
import java.util.Arrays;
public class Graph {
private ArrayList<String> vertexList; //存储顶点的集合
private int[][] edges; //存储图对应的临结矩阵
private int numOfEdges; //表示边的数目
public static void main(String[] args) {
int n=5;
String Vertexs[]={
"A","B","C","D","F"};
Graph graph=new Graph(n);
for(String vertex:Vertexs){
graph.insertVertex(vertex);
}
//添加边
//A-B A-C B-C B-D B-E
graph.insertEdge(0, 1, 1); // A-B
graph.insertEdge(0, 2, 1); //
graph.insertEdge(1, 2, 1); //
graph.insertEdge(1, 3, 1); //
graph.insertEdge(1, 4, 1); //
//显示
graph.showGraph();
}
//构造器
public Graph(int n){
edges=new int[n][n];
vertexList=new ArrayList<String>(n);
numOfEdges=0;
}
//插入节点
public void insertVertex(String vertex){
vertexList.add(vertex);
}
/**
* 添加边
* @param v1 第一个顶点的下标
* @param v2 第二个顶点的下标
* @param weight 权值
*/
public void insertEdge(int v1,int v2,int weight){
edges[v1][v2]=weight;
edges[v2][v1]=weight;
numOfEdges++;
}
//返回节点的个数
public int getNumOfVertex(){
return vertexList.size();
}
//得到边的个数
public int getNumOfEdges(){
return numOfEdges;
}
//返回节点i对应的下标
public String getValueByIndex(int i){
return vertexList.get(i);
}
//返回v1 v2的权值
public int getWeight(int v1,int v2){
return edges[v1][v2];
}
//显示图对应的矩阵
public void showGraph() {
for(int[] link : edges) {
System.err.println(Arrays.toString(link));
}
}
}
//对一个结点进行广度优先遍历的方法
private void bfs(boolean[] isVisited, int i) {
int u ; // 表示队列的头结点对应下标
int w ; // 邻接结点w
//队列,记录结点访问的顺序
LinkedList queue = new LinkedList();
//访问结点,输出结点信息
System.out.print(getValueByIndex(i) + "=>");
//标记为已访问
isVisited[i] = true;
//将结点加入队列
queue.addLast(i);
while( !queue.isEmpty()) {
//取出队列的头结点下标
u = (Integer)queue.removeFirst();
//得到第一个邻接结点的下标 w
w = getFirstNeighbor(u);
while(w != -1) {
//找到
//是否访问过
if(!isVisited[w]) {
System.out.print(getValueByIndex(w) + "=>");
//标记已经访问
isVisited[w] = true;
//入队
queue.addLast(w);
}
//以u为前驱点,找w后面的下一个邻结点
w = getNextNeighbor(u, w); //体现出我们的广度优先
}
}
}
//遍历所有的结点,都进行广度优先搜索
public void bfs() {
isVisited = new boolean[vertexList.size()];
for(int i = 0; i < getNumOfVertex(); i++) {
if(!isVisited[i]) {
bfs(isVisited, i);
}
}
}
//深度优先遍历算法
//i 第一次就是 0
private void dfs(boolean[] isVisited, int i) {
//首先我们访问该结点,输出
System.out.print(getValueByIndex(i) + "->");
//将结点设置为已经访问
isVisited[i] = true;
//查找结点i的第一个邻接结点w
int w = getFirstNeighbor(i);
while(w != -1) {
//说明有
if(!isVisited[w]) {
dfs(isVisited, w);
}
//如果w结点已经被访问过
w = getNextNeighbor(i, w);
}
}
//对dfs 进行一个重载, 遍历我们所有的结点,并进行 dfs
public void dfs() {
isVisited = new boolean[vertexList.size()];
//遍历所有的结点,进行dfs[回溯]
for(int i = 0; i < getNumOfVertex(); i++) {
if(!isVisited[i]) {
dfs(isVisited, i);
}
}
}
//非递归写法
public static int binarySearch(int[] arr,int target){
int left=0;
int right=arr.length-1;
while(left<=right){
int mid=(right+left)/2;
if(arr[mid]==target){
return mid;
}else if(arr[mid]>target){
right=mid-1;
}else if(arr[mid]<target){
left=mid+1;
}
}
return -1;
}
把原问题分解为几个和原问题相同的子问题
快速排序
归并排序
背包问题:
一个背包:容量四磅,有如下物品
1要求装入背包总价值最大且重量不超出
2要求装入的物品不能重复
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tnms4Wib-1618453474296)(D:\学习笔记\刷题笔记截图\image-20210414114433724.png)]
动态规划:
将大问题划分为小问题,经分解得到的子问题往往不是相互独立的,下一个问题建立在上一个问题的基础上
}
//返回v1 v2的权值
public int getWeight(int v1,int v2){
return edges[v1][v2];
}
//显示图对应的矩阵
public void showGraph() {
for(int[] link : edges) {
System.err.println(Arrays.toString(link));
}
}
}
#### 1.3 广度优先
```java
//对一个结点进行广度优先遍历的方法
private void bfs(boolean[] isVisited, int i) {
int u ; // 表示队列的头结点对应下标
int w ; // 邻接结点w
//队列,记录结点访问的顺序
LinkedList queue = new LinkedList();
//访问结点,输出结点信息
System.out.print(getValueByIndex(i) + "=>");
//标记为已访问
isVisited[i] = true;
//将结点加入队列
queue.addLast(i);
while( !queue.isEmpty()) {
//取出队列的头结点下标
u = (Integer)queue.removeFirst();
//得到第一个邻接结点的下标 w
w = getFirstNeighbor(u);
while(w != -1) {//找到
//是否访问过
if(!isVisited[w]) {
System.out.print(getValueByIndex(w) + "=>");
//标记已经访问
isVisited[w] = true;
//入队
queue.addLast(w);
}
//以u为前驱点,找w后面的下一个邻结点
w = getNextNeighbor(u, w); //体现出我们的广度优先
}
}
}
//遍历所有的结点,都进行广度优先搜索
public void bfs() {
isVisited = new boolean[vertexList.size()];
for(int i = 0; i < getNumOfVertex(); i++) {
if(!isVisited[i]) {
bfs(isVisited, i);
}
}
}
//深度优先遍历算法
//i 第一次就是 0
private void dfs(boolean[] isVisited, int i) {
//首先我们访问该结点,输出
System.out.print(getValueByIndex(i) + "->");
//将结点设置为已经访问
isVisited[i] = true;
//查找结点i的第一个邻接结点w
int w = getFirstNeighbor(i);
while(w != -1) {
//说明有
if(!isVisited[w]) {
dfs(isVisited, w);
}
//如果w结点已经被访问过
w = getNextNeighbor(i, w);
}
}
//对dfs 进行一个重载, 遍历我们所有的结点,并进行 dfs
public void dfs() {
isVisited = new boolean[vertexList.size()];
//遍历所有的结点,进行dfs[回溯]
for(int i = 0; i < getNumOfVertex(); i++) {
if(!isVisited[i]) {
dfs(isVisited, i);
}
}
}
//非递归写法
public static int binarySearch(int[] arr,int target){
int left=0;
int right=arr.length-1;
while(left<=right){
int mid=(right+left)/2;
if(arr[mid]==target){
return mid;
}else if(arr[mid]>target){
right=mid-1;
}else if(arr[mid]<target){
left=mid+1;
}
}
return -1;
}
把原问题分解为几个和原问题相同的子问题
快速排序
归并排序
背包问题:
一个背包:容量四磅,有如下物品
1要求装入背包总价值最大且重量不超出
2要求装入的物品不能重复
[外链图片转存中…(img-Tnms4Wib-1618453474296)]
动态规划:
将大问题划分为小问题,经分解得到的子问题往往不是相互独立的,下一个问题建立在上一个问题的基础上