算法学习笔记

导学

程序=算法+数据结构

image-20240130170010852

二分法查找

image-20240130175000010

升序排列的数字:先将索引放在first和end上,找见小于等于middle的索引,当middle对应值大于goal时,将end放在middle-1上

 public class test1 {
     public static void main(String[] args){
         int[] arr = {1,3,5,7,9,11,13};
         int result = dichotomy(arr,11);
         System.out.println(result);
     }
     public static int dichotomy(int[] arr,int result){
         int first = 0,end = arr.length - 1;
         while (first <= end ){
         //当要查找的数在第一位时,会出现first=end ,此时若条件为(first<end)则会停止查询,直接返回-1,如下图
             int middle = (first + end)/>>>1;
             //当end取到正整数最大值时,假设result在右侧,则会出现first+end超出正数范围的数,就会成为负数,正常数学计算就是数轴形式,但是java里面第一位视为符号位,是一个闭合圆形 ,所以用到了无符号右移,右移一次就是除以2的效果
             //int middle = (first + end)>>>1;
             if(arr[middle] > result){
                 end = middle -1;
             } else if(arr[middle] < result){
                 first = middle + 1;
             }else{
                 return middle;
             }
         }
         return -1;
     }
 }

 public class test2 {
     public static void main(String[] args){
         int[] arr = {1,3,5,7,9,11,13};
         int result = redichotomy(arr,11);
         System.out.println(result);
     }
     public static int redichotomy(int[] arr,int result){
         int first = 0,end = arr.length;
         while (first < end ){
             int middle = (first + end)>>>1;
             if(arr[middle] > result){
                 end = middle;
             } else if(arr[middle] < result){
                 first = middle + 1;
             }else{
                 return middle;
             }
         }
         return -1;
     }
 }
 //第二种就是一个修改,第一种要注意边界,第二种要注意没有的值,假如10,first=end时就会存在死循环

image-20240130220550620

二分查找平衡版

因为result在最左边时,只执行第一个if,但是在最右边时,会执行第二个if,导致左边执行L次,右边执行2L次,左右不平衡,

 public static int reDichotomy(int[] arr, int result){
     int first = 0,end = arr.length;
     while (1 < end - first ){
         int middle = (first+end)>>>1;
         if(result < arr[middle]){
             end = middle;
         }else {
             first = middle;
         }
     }
     if(arr[first] == result){
         return first;
     }else {
         return -1;
     }
 }
 //优点:平均比较次数减少,适合多数据使用
 //缺点:最优情况不存在

Java二分法逻辑

 //没有找到的返回值是-(插入点+1)
 插入点就是first的值
 +1的原因就是当first=0时在java里面0和-0无法区分

image-20240131114033969

数组拷贝

 import java.util.Arrays;
 public class test3 {
     public static void main(String[] args) {
         int[] arr = {1,3,5,7,9,11,13};
         int result = 8;
         int i = Arrays.binarySearch(arr,result);
         if(i<0){
             int index = Math.abs(i+1);
             int[] brr = new int[arr.length+1];
             System.arraycopy(arr,0,brr,0,index);
             brr[index] = result;
             System.arraycopy(arr,index,brr,index+1,arr.length-index);
             System.out.println(Arrays.toString(brr));
         }
     }
 }

元素重复

 public static int findCandidate(int[] arr,int result){
     int first = 0,end = arr.length - 1;
     int Candidate = -1;
     while (first <= end ){
         int middle = (first + end)>>>1;
         if(arr[middle] > result){
             end = middle -1;
         } else if(arr[middle] < result){
             first = middle + 1;
         }else{
             Candidate = middle;
             end = middle -1;
             //右侧时改first就可以了
             //first = middle + 1;
         }
     }
     return Candidate;
 }
 //给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
 //如果数组中不存在目标值 target,返回 [-1, -1]。
 class Solution {
     public int[] searchRange(int[] nums, int target) {
             int low = 0;
             int high = nums.length - 1;
             while (low <= high) {
                 int mid = (low+high)>>>1;
                 if (nums[mid] == target) {
                     //找到中间值的时候,就让左右两边开始缩小范围
                     while (nums[low] != target) {
                         low++;
                     }
                     //左边找的就是左边界
                     while (nums[high] != target) {
                         high--;
                     }
                     //右边找的就是右边界
                     return new int[] { low, high };
                     //这里找到之后就是返回左边界和右边界
                 }
                 else if (nums[mid] < target) {
                     low = mid + 1;
                 }
                 else {
                     high = mid - 1;
                 }
             }
             return new int[] { -1, -1 };
     }
 }

 ​
 //给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
 //由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。
 //注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5
 class Solution {
     public int mySqrt(int x) {
         int first = 0 ,end = x;
         int result = -1;
         while(first <= end){
             int middle = (first + end)>>>1;
             if((long)middle*middle <= x){
                 result = middle;
                 first = middle + 1;
             }else{
                 end = middle - 1;
             }
         }
         return result;
     }
 ​

 //给你一个正整数 num 。如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
 //完全平方数 是一个可以写成某个整数的平方的整数。换句话说,它可以写成某个整数和自身的乘积。
 //不能使用任何内置的库函数,如  sqrt
 class Solution {
     public boolean isPerfectSquare(int num) {
 int first = 0 , end = num ;
         while(first <= end){
             int middle = (first+end)>>>1;
             if((long) middle*middle < num){
                 first = middle + 1;
             }else if((long) middle*middle>num){
                 end = middle - 1;
             }else{
                 return true;
             }
         }
         return false;
     }
 }

单侧重复,改进返回值

 //返回-1没有什么实际含义,返回first就是返回大于result的最近元素的索引
 public static int findCandidate(int[] arr,int result){
     int first = 0,end = arr.length - 1;
     while (first <= end ){
         int middle = (first + end)>>>1;
         if(arr[middle] >= result){
         //<=
             end = middle -1;
         } else{
             first = middle + 1;
         }
     }
     return first;
     //return first + 1;
     //向左查找的时候,如果存在,就是查到最左侧的重复项,向右查找就是查到最右侧的重复项
     //如果不存在,向左查找大于result的最小值,向右查找小于result的最大值
 }

image-20240131212739174

image-20240131214931020

两侧重复,有重有漏(对于正中间来说)

 class Solution {
     public int countTarget(int[] nums, int target) {
         if (nums.length == 0) return 0;
         return binarySearch(nums, target + 1) - binarySearch(nums, target);
     }
 ​
     private int binarySearch(int[] nums, int target) {
         int left = 0, right = nums.length;
         while (left < right) {
             int mid = (left + right)>>>1;
             if (nums[mid] < target) {
                 left = mid + 1;
             } else {
                 right = mid;
             }
         }
         return right;
     }
 }

如何衡量算法的好坏

事前分析法:

1.分析算法最差的情况,比较运算速率,

2.假设每行语句执行时间一样

 //线性查找
 public static int LinearSearch(int[] arr,int result){
     for (int i = 0; i < arr.length; i++) {
         if(result == arr[i]){
             return i;
         }
     }
     return -1;
 }
 //运行时间为3n+3次
 ​

 public static int dichotomy(int[] arr,int result){
     int first = 0,end = arr.length - 1;
     while (first <= end ){
         int middle = (first + end)>>>1;
         if(arr[middle] > result){
             end = middle -1;
         } else if(arr[middle] < result){
             first = middle + 1;
         }else{
             return middle;
         }
     }
     return -1;
 }
 //运行时间:假设有八个元素,运行4次,16个元素,运行5次,实际次数为(log2*n)*5+4

数据少的时候线性查找好用,数据多的时候,二分查找好用

时间复杂度

一个算法的执行,随着数据规模扩大,而增长的时间成本

不依赖于环境因素

image-20240131102550504

大O表示法:O(n)

渐近上界就是代码执行的最差情况,渐近下界就是代码执行的最优情况

渐近紧界:既能代表算法的最差情况也能代表最优情况

image-20240131103304722

找见原函数的最高项拿出来,然后改变系数

image-20240131103816967

image-20240131104021816

image-20240131104156455

image-20240131104345212

空间复杂度

与时间复杂度类似,一般也使用大O表示法,一个算法执行随数据规模增大,而增长的额外空间成本

 public static int dichotomy(int[] arr,int result){
     int first = 0,end = arr.length - 1;
     while (first <= end ){
         int middle = (first + end)>>>1;
         if(arr[middle] > result){
             end = middle -1;
         } else if(arr[middle] < result){
             first = middle + 1;
         }else{
             return middle;
         }
     }
     return -1;
 }
 //三个int占了十二字节,middle会数据覆盖,所以就不会产生新的空间

image-20240131105423530

 

数组

假设知道了数组的起始位置,就可以计算出他的存储位置。

  • 起始地址BaseAddress

$$
BaseAddress + i*size
$$

  • i是索引,size是每个元素占用字节

空间占用

  • 8字节markword

  • 4字节类指针

  • 4字节数组大小(决定了数组最大容量是2的32次方)

  • 数组元素+对齐字节(不足的要补齐)

  •  8+4+4+5*4+4

image-20240204163846387

随机访问

根据索引查找元素,时间复杂度是O(1)

 package array;
 ​
 public class Array1 {
     private int size = 0;
     private int volume = 8;//数组容积
     private int[] array = new int[volume];
     //添加元素
     public void addLast(int element){
         add(size,element);
     }
     //添加元素,插入元素
     public void add(int index , int element){
         if(index<0||index>size){
             System.out.println("Out of index range");
         }else{
             System.arraycopy(array,index,array,index+1,size-index);
         }
         array[index]=element;
         size++;
     }
     //查询元素
     public int get(int index){
         return array[index];
     }
     
 }

数组遍历

 package array;
 ​
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.function.Consumer;
 import java.util.stream.IntStream;
 ​
 public class Array1 implements Iterable<Integer> {
     private int size = 0;
     private int volume = 8;//数组容积
     private int[] array = new int[volume];
     public void addLast(int element){
         add(size,element);
     }
     public void add(int index , int element){
         if(index<0||index>size){
             System.out.println("Out of index range");
         }else{
             System.arraycopy(array,index,array,index+1,size-index);
         }
         array[index]=element;
         size++;
     }
     //1.普通遍历
     public int get(int index){
         return array[index];
     }
     //2.Consumer
     public void foreach(Consumer<Integer> consumer){
         for (int i = 0; i < array.length; i++) {
             consumer.accept(array[i]);
         }
     }
     //3.迭代器遍历
 ​
     @Override
     public Iterator<Integer> iterator() {
         return new Iterator<Integer>(){
             int i = 0;
             @Override
             public boolean hasNext() {//判断有没有下一个元素
                 return i < size;
             }
 ​
             @Override
             public Integer next() {//返回当前元素,并且移动到下一个元素
                 return array[i++];
             }
         };
     }
     //4.转换成流来遍历
     public IntStream stream(){
         return IntStream.of(Arrays.copyOfRange(array,0,this.size));//size含头不含尾
     }
 }

 package array;
 ​
 public class test1 {
     public static void main(String[] args){
         Array1 array1 = new Array1();
         for(int i = 1; i < 6 ; i++){
             array1.addLast(i);
         }
         array1.add(1,6);
     
         array1.forEach(System.out::println);
 ​
         for (Integer element : array1) {
             System.out.println(element);
         }
         
         array1.stream().forEach(System.out::println);
     }
 }

删除元素

双指针其实找的并不是重复项,而是寻找不重复内容,让其组成新数组

双指针指定删除

 class Solution {
     public int removeElement(int[] nums, int val) {
         int length = nums.length;
         if(length == 0){
             return 0;
         } else{
             int left =0;
             for(int right = 0;right<length;right++){
                 if(nums[right] != val){
                     nums[left] = nums[right];
                     left++;
                 }
             }
             return left;
         }
     }
 }

 //给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
 class Solution {
     public void moveZeroes(int[] nums) {
         if(nums.length == 0){
             return;
         }
         int i = 0 , j = 0 ;
         for(j = 0 ; j < nums.length ; j++){
             if(nums[j] != 0){
                 nums[i] = nums[j];
                 i++;
             }
         }
         //上半部分就是双指针移动元素,下半部分就是从数组最后一个非零整数开始将他后面的元素都变成0
         for(int k = i ; i < nums.length ; i ++){
            nums[i] =  0;
         }
     }
 }

双指针删除重复项

 class Solution {
     public int removeDuplicates(int[] nums) {
         int length = nums.length;
         if(length ==0){
             return 0;
         }else{
             int left = 1;          
             for(int right = 1;right<length;right++){
                 if(nums[right-1] != nums[right]){
                    nums[left] = nums[right];
                    ++left;
                     }
                 }
                 return left;
            }          
      }
 }        

给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。

 class Solution {
     public boolean backspaceCompare(String s, String t) {
         return change(s).equals(change(t));
     }
     private String change(String string){
         //使用栈的方式来解决,思路就是:当字符不为#的时候,就加入栈,若为#,先判度栈是否为空,若不为空就删除栈总长度的前一个元素,就是#之前的元素,两个栈处理之后转换成字符串进行比较
         StringBuffer ret = new StringBuffer();
         int length = string.length();
         for(int i = 0 ; i < length ; i ++){
             char c = string.charAt(i);
             if(c != '#'){
                 ret.append(c);
             }else{
                 if(ret.length() > 0){
                     ret.deleteCharAt(ret.length()-1);
                 }
             }
         }
         return ret.toString();
     }
 }

两数求和

 class Solution {
     public int[] twoSum(int[] nums, int target) {
         int length = nums.length;
         if(length == 0 ){
             return new int[0];
         }
         for(int i = 0;i<length;i++){
             for(int j = i+1;j<length;j++){
                 if((nums[i] + nums[j]) == target){
                     return new int[]{i,j};
                 }
             }
         }
         return new int[0];
     }
 }

java数组快速复制删除

 public int delete(int index){
     int deleted = array[index];
     if(deleted >=0 &&deleted < size-1){
         System.arraycopy(array,index + 1,array,index,size-index-1);
     }else {
         return -1;
     }
     return deleted;
 }
 //这两个都是用来比较数组的API但是比较之前需要加入maven下的两个依赖
 assertEquals(3,delete);
 assertIterableEquals(List.of(1,2,4,5),array1);

image-20240210203754246

加一问题

  • 如果最后一位不是9,那么直接+1

  • 如果最后一位是9,且前面有不是9的元素,那么从后向前第一个不是9的元素+1,之后的元素全部变为0

  • 如果整个数组都是9,那么创建一个新数组,让第一个元素为1,之后的全部为0

 class Solution {
     public int[] plusOne(int[] digits) {
         int n = digits.length;
         if(digits[n-1] != 9){
             digits[n-1]++;
             return digits;
         }
         for(int i = n - 1 ; i >= 0 ; i -- ){
             if(digits[i] != 9){
                 digits[i] ++;
         for(int j = i + 1 ; j < n ; j ++ ){
                     digits[j] = 0;
                 }
                 return digits;
             }
         }
         int[] arr = new int[n + 1];
         arr[0] = 1;
         for(int m = 1 ; m < n + 1;m++){
             arr[m] = 0;
         return arr;
     }
 }

数组合并

 class Solution {
     public void merge(int[] nums1, int m, int[] nums2, int n) {
         for(int i = 0 ; i < n ; i++,m++){
             nums1[m] = nums2[i];
         }
         sort(nums1,nums1.length-1);
     }
     private void sort(int[] nums1 , int end){
         if(end == 0 ){
             return;
         }
         int x = 0;
         for(int i = 0; i < end ; i++){
             if(nums1[i+1] < nums1[i]){
                 int middle = nums1[i];
                 nums1[i] = nums1[i+1];
                 nums1[i+1] = middle;
                 x = i;
             }
         }
         sort(nums1,x);
     }
 }

滑动窗口

 //给定一个含有 n 个正整数的数组和一个正整数 target 。
 //找出该数组中满足其总和大于等于 target 的长度最小的 连续
 //子数组
 // [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0
 class Solution {
     public int minSubArrayLen(int target, int[] nums) {
         int result = Integer.MAX_VALUE ;
         int sum = 0 ;
         for(int i = 0 , j = 0 ; j < nums.length ; j++){
             sum += nums[j];
             while(sum>=target){
                 result = Math.min(result, j - i + 1);
                 sum-=nums[i++];
             }
         }
         return result == Integer.MAX_VALUE ? 0 : result;
     }
 }

 class Solution {
     public int totalFruit(int[] fruits) {
         int[] window = new int[fruits.length + 1];
         int l = 0 , r = 0 , ans = 0 , same = 0;
         while(r < fruits.length){
             if(window[fruits[r++]]++ == 0)same++;
             while(same > 2){
                 if(window[fruits[l++]] -- == 1)same--;
             }
             ans = Math.max( r - l , ans );
         } 
         return ans;
     }
 }

二维数组

 int[][] array = {
     {1,2,3},
     {4,5,6},
 };
  • 二维数组占用20字节

  • 一维数组占用32字节

  • 他们在内存上都是连续的

缓存和局部性原理

缓存:内存和CPU的传输速度相差太多,所以有了缓存让数据暂时存在缓存中,而缓存的速度接近CPU,让CPU从缓存中读取

空间局部性:缓存在内存中读取时,将数据转换成缓存行,一个缓存行是64个字节

先行再列缓存可以将一行数据读入,并且CPU在读取时,是已经读入的

先列再行缓存是可以读入,但是CPU读取是发现相邻单位并没有被读入,而是下一行的元素同列元素被读入

螺旋矩阵

 //
 class Solution {
     public int[][] generateMatrix(int n) {
         int i , j;//i,j是两个指针
         int[][] arr = new int[n][n];
         int count = 1;//count就是要添加的元素
         int loop = 0 ;//loop就是循环的次数
         int start = 0 ;//start就是每次循环起始的位置
         while(loop++ < n / 2){
             //一分为2,所以遍历结束的条件就是n/2
             //先确定每次添加元素的结束条件,一般是左闭右开
             for(j = start ; j < n - loop ; j++){
                 arr[start][j] = count++;
             }
             //横向正向添加
             for(i = start ; i < n - loop ; i++){
                 arr[i][j] = count++;
             }
             //纵向正向添加,虽然添加到行最后一位的前一位,但是指针实际上等于最后一位才停止,所以里面不需要起始条件
             for( ; j > start ; j-- ){
                 arr[i][j] = count++;
             }
             //横向逆向添加
             for( ; i > start ; i-- ){
                 arr[i][j] = count++;
             }
             //纵向逆向添加
             //每添加完一组,就将起始位置做一次更改
             start++;
         }
         //当矩阵是奇数项的时候,需要向正中间添加元素,就是两个start的索引处
         if( n % 2 == 1 ){
             arr[start][start] = count;
         }
         return arr; 
     }
 }
 ​

image-20240404180728991

 //给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素
 //思路:将一个二维数组从左至右,从上到下,从右向左,从下至上,依次螺旋添加进入集合,最后返回集合
 class Solution {
     public List<Integer> spiralOrder(int[][] matrix) {
         //创建一个集合
         List<Integer> list = new ArrayList<Integer>();
         //判断数组长度,宽度是否为空
         if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
             return list;
         }
         //下面四个是四个边界,分别是最左边,最右边,最上边,以及最下边
         int left = 0 , right = matrix[0].length  - 1 , top = 0 , bottom  = matrix.length  - 1;
         while(left <= right && top <= bottom){
              //让列指针等于最左边,并且遍历到最右边,然后行指针不动,依次加入集合
             for(int column = left ; column <= right ; column++){
                 list.add(matrix[top][column]);
             }
             //让行指针不动,列指针从最上遍历至最下,
             for(int row = top + 1 ; row <= bottom ; row++){
                 list.add(matrix[row][right]);
             }
             
             if(left < right && top < bottom) {
                 //让行指针不动,列指针依次减小
                 for(int column = right - 1 ; column > left ; column--){
                     list.add(matrix[bottom][column]);
                 }
                 //让列指针不动,行指针依次减小
                 for(int row = bottom ; row > top ; row--){
                     list.add(matrix[row][left]);
                 }
             }
             //循环一次后,让范围缩小一圈
             left++;
             right--;
             top++;
             bottom--;
         }
         //zui'hou
         return list;
     }
 }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰哥的狗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值