Men的博客

欢迎光临!

0%

二分查找

寻找峰值

峰值元素是指其值大于左右相邻值的元素
方法一: 线性扫描
public int findPeakElement(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
if (nums[i] > nums[i + 1])
return i;
}
return nums.length - 1;
}
方法二:递归二分查找
我们可以将 nums 数组中的任何给定序列视为交替的升序和降序序列。通过利用这一点,以及“可以返回任何一个峰作为结果”的要求,我们可以利用二分查找来找到所需的峰值元素。
我们对二分查找进行一点修改。首先从数组 nums 中找到中间的元素 mid若该元素恰好位于降序序列或者一个局部下降坡度中,则说明峰值会在本元素的左边
就这样,我们不断地缩小搜索空间,直到搜索空间中只有一个元素,该元素即为峰值元素
public class Solution {
public int findPeakElement(int[] nums) {
return search(nums, 0, nums.length - 1);
}
public int search(int[] nums, int l, int r) {
if (l == r)
return l;
int mid = (l + r) / 2;
if (nums[mid] > nums[mid + 1])
return search(nums, l, mid);
return search(nums, mid + 1, r);
}
}

找出这个重复的数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
方法一:二分查找
class Solution {
public int findDuplicate(int[] nums) {
int n = nums.length;
int l = 1, r = n - 1, ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
int cnt = 0;
for (int i = 0; i < n; ++i) {
if (nums[i] <= mid) {
cnt++;
}
}
if (cnt <= mid) {
l = mid + 1;
} else {
r = mid - 1;
ans = mid;
}
}
return ans;

}

}
最优
int temp;
for(int i=0;i<nums.length;i++){
while (nums[i]!=i){
if(nums[i]==nums[nums[i]]){
return nums[i];
}
temp=nums[i];
nums[i]=nums[temp];
nums[temp]=temp;
}
}
return -1;

方法二:二进制
这个方法我们来将所有数二进制展开按位考虑如何找出重复的数,如果我们能确定重复数每一位是 1 还是 0 就可以按位还原出重复的数是什么。
class Solution {
public int findDuplicate(int[] nums) {
int n = nums.length, ans = 0;
int bit_max = 31;
while (((n - 1) >> bit_max) == 0) {
bit_max -= 1;
}
for (int bit = 0; bit <= bit_max; ++bit) {
int x = 0, y = 0;
for (int i = 0; i < n; ++i) {
if ((nums[i] & (1 << bit)) != 0) {
x += 1;
}
if (i >= 1 && ((i & (1 << bit)) != 0)) {
y += 1;
}
}
if (x > y) {
ans |= 1 << bit;
}
}
return ans;
}
}
方法三:快慢指针
我们先设置慢指针
slow 和快指针
fast ,慢指针每次走一步,快指针每次走两步
class Solution {
public int findDuplicate(int[] nums) {
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}

0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
二分法
class Solution {
public int missingNumber(int[] nums) {
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] == m) i = m + 1;
else j = m - 1;
}
return i;
}
}

旋转数组的最小数字

排序数组的查找问题首先考虑使用 二分法 解决,其可将遍历法的 线性级别 时间复杂度降低至 对数级别
public int minArray(int[] numbers) {
int i = 0, j = numbers.length - 1;
while (i < j) {
int m = (i + j) / 2;
if (numbers[m] > numbers[j]) i = m + 1;//m 一定在 左排序数组
else if (numbers[m] < numbers[j]) j = m;//m 一定在 右排序数组 中
else j–;
}
return numbers[i];
}

二分查找

全都统一成了两端都闭,便于记忆,只要修改两处即可变化出三种写法:
int binary_search(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
} else if(nums[mid] == target) {
// 直接返回
return mid;
}
}
// 直接返回
return -1;
}

在排序数组中查找数字

二分查找,找到左右闭合区间即可
重点在边界的限制上
class Solution {
public int search(int[] nums, int target) {
// 搜索右边界 right
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] <= target) i = m + 1;
else j = m - 1;
}
int right = i;
// 若数组中无 target ,则提前返回
if(j >= 0 && nums[j] != target) return 0;
// 搜索左边界 right
i = 0; j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] < target) i = m + 1;
else j = m - 1;
}
int left = j;
return right - left - 1;
}
}

查找value下标

给定一个有序的数组,查找value第一次出现的下标,不存在返回-1。

int BinarySearch(int array[], int n, int value)
{
int left = 0;
int right = n - 1;

while (left <= right)  
{
    int middle = left + ((right - left) >> 1);
    if (array[middle] >= value)  //因为是找到最小的等值下标,所以等于号放在这里
        right = middle - 1;
    else
        left = middle + 1;
}
return array[right + 1] == value ? right + 1 : -1;

}
如果问题改为查找value最后一次出现的下标呢?只需改动两个位置:
1.if (array[middle] >= value)中的等号去掉;
2.return中right+1改为left-1。
给定一个有序的数组,查找最接近value且大于value的数的下标(如果该数存在多个,返回第一个下标),不存在返回-1。

int BinarySearch(int array[], int n, int value)
{
int left = 0;
int right = n - 1;

while (left <= right)  
{
    int middle = left + ((right - left) >> 1);
    if (array[middle] > value)
        right = middle - 1;
    else
        left = middle + 1;
}
return array[right + 1] > value ? right + 1 : -1;

}
如果问题改为查找最接近value且小于value的数的下标(如果该数存在多个,返回最后一个下标)呢?只需改动两个位置:
1.if (array[middle] > value)加入一个等号;
2.return array[right + 1] > value ? right + 1 : -1;改为return array[left-1] < value ? left - 1 : -1;。

总结

  二分算法所操作的区间,是左闭右开,还是左闭右闭,需要在循环体跳出判断中,以及每次修改left,,right区间值这两个地方保持一致,否则就可能出错。

给一个数组,找出这样一些数,这个数左边的数都比它小,右边的数都比它大

排序

冒泡排序

    思想:每次遍历,相邻两个比较大小,修改位置
       复杂度:n平方
       function maopao(arr) {
           for (var i = 0; i < arr.length; i++) {
               for (var j = 0; j < arr.length; j++) {
                   if (arr[i] > arr[j]) {
                       var temp = arr[i];
                       arr[i] = arr[j];
                       arr[j] = temp;
                   }
               }
           }
       }

插入排序

    思想:每次往最小数组里插入一个数据,并保证数组是排序的
       (1) 将这个序列的第一个元素R0视为一个有序序列; 
       (2) 依次把 , , … ,  插入到这个有序序列中; 
       (3) 将插入到有序序列中时,前 i-1 个数是有序的,将和 ~ 从后往前进行比较,确定要插入的位置。
        public static int[] insertSOrt(int[] arr) {
              int len = arr.length;
              int preIndex,cur;
              for (int i = 0; i < len; i++) {
                 preIndex = i - 1;
                 cur = arr[i];
                 while (preIndex > 0 && arr[preIndex] > cur) {
                      arr[preIndex +1] = arr[preIndex];
                      preIndex--;
                 }
                 arr[preIndex+1] = cur;
              }
              return arr;
          }

归并排序

    作为一种典型的分而治之思想的算法应用
    将一个数组排序,可以先递归的将它分成两半分别排序,然后再将结果归并起来。
    缺点:需要额外的空间
    从下往上的归并排序:将待排序的数列分成若干个长度为1的子数列,然后将这
    些数列两两合并;得到若干个长度为2的有序数列,再将这些数列两两合并;得
    到若干个长度为4的有序数列,再将它们两两合并;直接合并成一个数列为止。
    这样就得到了我们想要的排序结果
     public int[] sort(int[] sourceArray) throws Exception {
           // 对 arr 进行拷贝,不改变参数内容
           int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);

           if (arr.length < 2) {
               return arr;
           }
           int middle = (int) Math.floor(arr.length / 2);
           // 分开
           int[] left = Arrays.copyOfRange(arr, 0, middle);
           int[] right = Arrays.copyOfRange(arr, middle, arr.length);
           // 合并
           return merge(sort(left), sort(right));
    }

    protected int[] merge(int[] left, int[] right) {
           int[] result = new int[left.length + right.length];
           int i = 0;
           while (left.length > 0 && right.length > 0) {
               if (left[0] <= right[0]) {
                   result[i++] = left[0];
                   left = Arrays.copyOfRange(left, 1, left.length);
               } else {
                   result[i++] = right[0];
                   right = Arrays.copyOfRange(right, 1, right.length);
               }
           }

           while (left.length > 0) {
               result[i++] = left[0];
               left = Arrays.copyOfRange(left, 1, left.length);
           }

           while (right.length > 0) {
               result[i++] = right[0];
               right = Arrays.copyOfRange(right, 1, right.length);
           }

           return result;
       }

快速排序

    分而治之思想在排序算法。本质上来看,快速排序应该算是在冒泡排序基础上的递归分治法。
       先是把第一个挖出来,作为基值;然后先是从右向左,对比基值,如果如果小,那么放到坑里;然后在从左往右对比基值,如果大,那么放到坑里;当换完一轮了后,把基值放到坑里;然后在根据基值,分左右两组,递归排序;
       通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比
       另外一部分的所有数据都要小,继续对长度较短的序列进行同样的分割,最后到
       达整体有序。在排序过程中,由于已经分开的两部分的元素不需要进行比较,故
       减少了比较次数,降低了排序时间。
      private static void quickSort(int[] arr, int low, int high) {

          if (low < high) {
              // 找寻基准数据的正确索引位置
              int index = getIndex(arr, low, high);
              // 进行迭代对index之前和之后的数组进行相同的操作使整个数组变成有序
              quickSort(arr, low, index - 1); // 左半边排序
              quickSort(arr, index + 1, high); // 右半边排序
          }

      }

      private static int getIndex(int[] arr, int low, int high) {
          // 基准数据
          int tmp = arr[low];
          while (low < high) {
              // 当队尾的元素大于等于基准数据时,向前挪动high指针
              while (low < high && arr[high] >= tmp) {
                  high--;
              }
              // 如果队尾元素小于tmp了,需要将其赋值给low
              arr[low] = arr[high];
              // 当队首元素小于等于tmp时,向前挪动low指针
              while (low < high && arr[low] <= tmp) {
                  low++;
              }
              // 当队首元素大于tmp时,需要将其赋值给high
              arr[high] = arr[low];

          }
          // 跳出循环时low和high相等,此时的low或high就是tmp的正确索引位置
          // 由原理部分可以很清楚的知道low位置的值并不是tmp,所以需要将tmp赋值给arr[low]
          arr[low] = tmp;
          return low; // 返回tmp的正确位置
      }

拓扑排序

    它是对有向图的顶点排成一个线性序列。

堆排序(大顶堆、小顶堆)

    堆排序可以说是一种利用堆的概念来排序的选择排序。分为两种方法:
     大顶堆:每个节点的值都大于或等于其子节点的值,在堆排序算法中用于升序排列
     小顶堆:每个节点的值都小于或等于其子节点的值,在堆排序算法中用于降序排列
     堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大
     值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将
     剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执
     行,便能得到一个有序序列了
     
     var len;    //因为声明的多个函数都需要数据长度,所以把len设置成为全局变量

     function buildMaxHeap(arr) {   //建立大顶堆
         len = arr.length;
         for (var i = Math.floor(len/2); i &gt;= 0; i--) {
             heapify(arr, i);
         }
     }

     function heapify(arr, i) {     //堆调整
         var left = 2 * i + 1,
             right = 2 * i + 2,
             largest = i;

         if (left < len && arr[left] > arr[largest]) {
             largest = left;
         }

         if (right < len && arr[right] > arr[largest]) {
             largest = right;
         }

         if (largest != i) {
             swap(arr, i, largest);
             heapify(arr, largest);
         }
     }

     function swap(arr, i, j) {
         var temp = arr[i];
         arr[i] = arr[j];
         arr[j] = temp;
     }

     function heapSort(arr) {
         buildMaxHeap(arr);

         for (var i = arr.length-1; i > 0; i--) {
             swap(arr, 0, i);
             len--;
             heapify(arr, 0);
         }
         return arr;
     }

桶排序

    知道最大最小值,可以生成一个最大最小值的一个桶的map,key是值,value是出现的次数
    for(i=1;i<=n;i++)//循环读入n个数,并进行桶排序
   {
       scanf("%d",&t);  //把每一个数读到变量t中
       book[t]++;  //进行计数,对编号为t的桶放一个小旗子
   }
   for(i=1000;i>=0;i--)  //依次判断编号1000~0的桶
       for(j=1;j<=book[i];j++)  //出现了几次就将桶的编号打印几次
            printf("%d ",i);
   getchar();getchar();

计数排序

按指定要求排序后输出

给字符串,内含很多整数,按指定要求排序后输出指定位置上的整数;
两个有序数组,找出合并后的中位数
获取数组中最值(最大值/最小值)
分治 + 最小堆排序的思想
局部淘汰法。选取前100个元素,并排序,记为序列L。然后一次扫描剩余的元素x,
与排好序的100个元素中最小的元素比,如果比这个最小的要大,那么把这个最小的元素删除
,并把x利用插入排序的思想,插入到序列L中。依次循环,知道扫描了所有的元素。复杂度为O(100w*100)。
原理是最小堆排序
https://blog.csdn.net/zyq522376829/article/details/47686867
求数组中第k大的数
建立一个大小为K的最小堆,堆顶就是第K大的数
例如,假设有10个数,要求求第3大的数,第一步选取任意的3个数,比如说是前3个,
将这3个数建成最小堆,然后从第4个数开始,与堆顶
的数比较,如果比堆顶的数要小,那么这个数就不要,如果比堆顶的数大,
则舍弃当前的堆顶而将这个数作为新的堆顶,并再去维护堆

旋转数组查找;

public static int minNumberInRotateArray(int[] rotateArray)
   {
       if (rotateArray.Length == 0) return 0;
       else if (rotateArray.Length == 1||rotateArray[0]<rotateArray[rotateArray.Length-1]) return rotateArray[0];
       else
       {
           int low = 0;
           int high = rotateArray.Length - 1;
           while (low<=high)
           {
               int mid = (int)Math.Floor((double)(low + high) / 2);
               if (rotateArray[mid] > rotateArray[0]) low = mid + 1;
               else if (rotateArray[mid] < rotateArray[0]) high = mid - 1;
               else low = low + 1;
           }

           return low == rotateArray.Length ? rotateArray[low - 1] : rotateArray[low];
       }
   }

##常见算法
广度优先搜索
深度优先搜索
递归
回溯
贪婪算法
动态规划
线性规划
区间规划
约束规划
自底向上
自定向下

搜索技巧

回溯
递归
剪枝

基础技巧

分治
倍增
二分
贪心

模拟

  1. 加油站
  2. LRU缓存机制
  3. 快乐数
  4. 生命游戏
  5. 两整数之和
  6. Fizz Buzz
    两个人掷硬币,先得到正面的人赢,游戏到有人赢结束。问先掷硬币的人赢的概率