当前位置:   article > 正文

算法-排序与双指针_双指针法排序

双指针法排序

n平方复杂度的排序有哪些?

n平方复杂度的排序算法包括:

  1. 冒泡排序(Bubble Sort):比较相邻的元素,如果前一个比后一个大,就交换它们的位置,重复进行直到排序完成。

  2. 选择排序(Selection Sort):每次从未排序的元素中选择最小的元素,放到已排序的末尾,重复进行直到排序完成。

  3. 插入排序(Insertion Sort):将未排序的元素插入到已排序的合适位置,重复进行直到排序完成。

  4. 希尔排序(Shell Sort):将待排序的元素按照一定的间隔分组,对每组进行插入排序,逐渐缩小间隔,直到间隔为1,最后进行一次插入排序。

  5. 快速排序(Quick Sort):选择一个基准元素,将小于基准元素的放在左边,大于基准元素的放在右边,对左右两个子序列递归进行快速排序。

这些排序算法的时间复杂度都是O(n^2),其中冒泡排序、选择排序和插入排序是比较简单的排序算法,但效率较低,适用于小规模数据的排序;希尔排序是插入排序的改进版,效率比前三种算法要高一些;快速排序是一种高效的排序算法,虽然最坏情况下时间复杂度也是O(n^2),但平均情况下时间复杂度为O(nlogn),是常用的排序算法之一。

 如何实现冒泡排序,如何进行优化?

冒泡排序是一种简单的排序算法,其基本思想是通过相邻元素之间的比较和交换来将较大的元素逐步“冒泡”到数组的末尾,从而实现排序。具体实现过程如下:

  1. 从数组的第一个元素开始,依次比较相邻的两个元素,如果前一个元素比后一个元素大,则交换它们的位置。

  2. 继续比较下一对相邻元素,重复上述操作,直到将最大的元素“冒泡”到数组的末尾。

  3. 重复执行上述操作,直到整个数组排序完成。

以下是一个简单的JavaScript实现冒泡排序的示例代码:

  1. function bubbleSort(arr) {
  2. const len = arr.length;
  3. for (let i = 0; i < len - 1; i++) {
  4. for (let j = 0; j < len - i - 1; j++) {
  5. if (arr[j] > arr[j + 1]) {
  6. [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
  7. }
  8. }
  9. }
  10. return arr;
  11. }
'
运行

 在这个实现中,bubbleSort函数接受一个数组arr作为参数,并返回一个排序后的数组。这个函数使用两层循环来实现冒泡排序,外层循环控制排序的轮数,内层循环控制每一轮比较的次数。如果相邻的两个元素顺序不对,则交换它们的位置。

冒泡排序的时间复杂度为O(n^2),在处理大规模数据时效率较低。为了提高冒泡排序的效率,可以进行以下优化:

  1. 设置标志位:如果在一轮排序中没有发生任何交换,说明数组已经有序,可以直接退出循环。

  2. 设置边界:每一轮排序后,最后一个元素已经是有序的,可以将下一轮排序的边界设置为上一轮排序的最后一个元素。

  3. 鸡尾酒排序:在冒泡排序的基础上,每一轮排序既从左往右进行比较和交换,又从右往左进行比较和交换,可以更快地将较小的元素“冒泡”到数组的前面。

这些优化可以提高冒泡排序的效率,但时间复杂度仍然是O(n^2)。因此,在处理大规模数据时,应该选择更高效的排序算法。

如何实现选择排序和插入排序? 

选择排序

选择排序的基本思想是每次从未排序的元素中选择最小的元素,将其放到已排序的元素末尾。具体实现过程如下:

  • 从数组的第一个元素开始,依次遍历数组中的每个元素,将当前元素设为最小值。

  • 继续遍历数组,如果找到比当前元素更小的元素,则将其设为最小值。

  • 遍历完成后,将最小值与数组的第一个元素交换位置,将其放到已排序的元素末尾。

  • 重复执行上述操作,直到整个数组排序完成。

以下是一个简单的JavaScript实现选择排序的示例代码:

  1. function selectionSort(arr) {
  2. const len = arr.length;
  3. for (let i = 0; i < len - 1; i++) {
  4. let minIndex = i;
  5. for (let j = i + 1; j < len; j++) {
  6. if (arr[j] < arr[minIndex]) {
  7. minIndex = j;
  8. }
  9. }
  10. [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
  11. }
  12. return arr;
  13. }
'
运行

 在这个实现中,selectionSort函数接受一个数组arr作为参数,并返回一个排序后的数组。这个函数使用两层循环来实现选择排序,外层循环控制排序的轮数,内层循环控制每一轮比较的次数。在每一轮比较中,找到未排序元素中的最小值,并将其放到已排序元素的末尾。

选择排序的时间复杂度为O(n^2),与冒泡排序相同。但是,选择排序的交换次数比冒泡排序少,因此在某些情况下,选择排序的效率可能会更高。

插入排序

插入排序的基本思想是将未排序的元素插入到已排序的元素中,使得已排序的元素仍然有序。具体实现过程如下:

  • 从数组的第二个元素开始,依次遍历数组中的每个元素,将当前元素插入到已排序的元素中。

  • 在已排序的元素中,从后往前遍历,找到当前元素应该插入的位置。

  • 将当前元素插入到已排序的元素中,使得已排序的元素仍然有序。

  • 重复执行上述操作,直到整个数组排序完成。

以下是一个简单的JavaScript实现插入排序的示例代码:

  1. function insertionSort(arr) {
  2. const len = arr.length;
  3. for (let i = 1; i < len; i++) {
  4. let j = i;
  5. while (j > 0 && arr[j] < arr[j - 1]) {
  6. [arr[j], arr[j - 1]] = [arr[j - 1], arr[j]];
  7. j--;
  8. }
  9. }
  10. return arr;
  11. }
'
运行

在这个实现中,insertionSort函数接受一个数组arr作为参数,并返回一个排序后的数组。这个函数使用两层循环来实现插入排序,外层循环控制排序的轮数,内层循环控制每一轮比较的次数。在每一轮比较中,找到当前元素应该插入的位置,并将其插入到已排序的元素中。

插入排序的时间复杂度为O(n^2),与冒泡排序和选择排序相同。但是,插入排序的交换次数比冒泡排序和选择排序少,因此在某些情况下,插入排序的效率可能会更高。

n*logn复杂读的排序有哪些?

n*logn复杂度的排序算法有以下几种:

  1. 快速排序

快速排序是一种基于分治思想的排序算法,其基本思想是选择一个基准元素,将数组分成两个子数组,其中一个子数组中的所有元素都小于基准元素,另一个子数组中的所有元素都大于基准元素。然后递归地对子数组进行排序,最终将整个数组排序完成。快速排序的时间复杂度为O(n*logn),但是在最坏情况下,时间复杂度可能会退化为O(n^2)。

    2. 归并排序

归并排序是一种基于分治思想的排序算法,其基本思想是将数组分成两个子数组,递归地对子数组进行排序,然后将两个有序的子数组合并成一个有序的数组。归并排序的时间复杂度为O(n*logn),但是需要额外的空间来存储临时数组。

   3. 堆排序

堆排序是一种基于堆的数据结构的排序算法,其基本思想是将数组构建成一个堆,然后依次将堆顶元素取出并放到已排序的元素中。堆排序的时间复杂度为O(n*logn),但是需要额外的空间来存储堆。

   4.希尔排序

希尔排序是一种基于插入排序的排序算法,其基本思想是将数组分成若干个子数组,对每个子数组进行插入排序,然后逐步缩小子数组的范围,最终将整个数组排序完成。希尔排序的时间复杂度为O(n*logn),但是其时间复杂度的分析比较困难。

以上是常见的n*logn复杂度的排序算法,它们都具有较高的效率和稳定性,可以应用于各种排序场景。

如何实现快速排序和归并排序?

快速排序的实现:

  1. 选择一个基准元素,通常选择第一个或最后一个元素。
  2. 将数组分成两个子数组,其中一个子数组中的所有元素都小于基准元素,另一个子数组中的所有元素都大于基准元素。
  3. 递归地对子数组进行排序,最终将整个数组排序完成。

代码实现:

  1. void quickSort(int arr[], int left, int right) {
  2. int i = left, j = right;
  3. int tmp;
  4. int pivot = arr[(left + right) / 2];
  5. /* partition */
  6. while (i <= j) {
  7. while (arr[i] < pivot)
  8. i++;
  9. while (arr[j] > pivot)
  10. j--;
  11. if (i <= j) {
  12. tmp = arr[i];
  13. arr[i] = arr[j];
  14. arr[j] = tmp;
  15. i++;
  16. j--;
  17. }
  18. };
  19. /* recursion */
  20. if (left < j)
  21. quickSort(arr, left, j);
  22. if (i < right)
  23. quickSort(arr, i, right);
  24. }

 归并排序的实现:

  1. 将数组分成两个子数组。
  2. 递归地对子数组进行排序。
  3. 将两个有序的子数组合并成一个有序的数组。

代码实现:

  1. void merge(int arr[], int left, int mid, int right) {
  2. int i, j, k;
  3. int n1 = mid - left + 1;
  4. int n2 = right - mid;
  5. /* create temp arrays */
  6. int L[n1], R[n2];
  7. /* copy data to temp arrays L[] and R[] */
  8. for (i = 0; i < n1; i++)
  9. L[i] = arr[left + i];
  10. for (j = 0; j < n2; j++)
  11. R[j] = arr[mid + 1 + j];
  12. /* merge the temp arrays back into arr[left..right]*/
  13. i = 0; /* initial index of first subarray */
  14. j = 0; /* initial index of second subarray */
  15. k = left; /* initial index of merged subarray */
  16. while (i < n1 && j < n2) {
  17. if (L[i] <= R[j]) {
  18. arr[k] = L[i];
  19. i++;
  20. }
  21. else {
  22. arr[k] = R[j];
  23. j++;
  24. }
  25. k++;
  26. }
  27. /* copy the remaining elements of L[], if there are any */
  28. while (i < n1) {
  29. arr[k] = L[i];
  30. i++;
  31. k++;
  32. }
  33. /* copy the remaining elements of R[], if there are any */
  34. while (j < n2) {
  35. arr[k] = R[j];
  36. j++;
  37. k++;
  38. }
  39. }
  40. void mergeSort(int arr[], int left, int right) {
  41. if (left < right) {
  42. int mid = left + (right - left) / 2;
  43. /* sort first and second halves */
  44. mergeSort(arr, left, mid);
  45. mergeSort(arr, mid + 1, right);
  46. /* merge the sorted halves */
  47. merge(arr, left, mid, right);
  48. }
  49. }

复杂度为n的排序算法有哪些?具体的思路是什么样的?

复杂度为n的排序算法有计数排序、桶排序和基数排序。计数排序的思路是统计每个元素出现的次数,然后根据元素出现的次数将元素放入相应的位置。具体实现时,需要先确定待排序数组中最大元素的值max,然后创建一个长度为max+1的计数数组count,遍历待排序数组,统计每个元素出现的次数,最后根据计数数组将元素放入相应的位置。

桶排序的思路是将待排序数组分成若干个桶,每个桶内部使用其他排序算法进行排序,最后将所有桶中的元素按照顺序依次放入待排序数组中。具体实现时,需要先确定桶的数量和每个桶的范围,然后遍历待排序数组,将每个元素放入相应的桶中,最后对每个桶内部使用其他排序算法进行排序,最后将所有桶中的元素按照顺序依次放入待排序数组中。

基数排序的思路是将待排序数组按照位数从低到高依次进行排序,具体实现时,需要先确定待排序数组中最大元素的位数maxDigit,然后从个位开始,对每一位进行排序,最后得到有序数组。具体实现时,可以使用桶排序对每一位进行排序,也可以使用计数排序对每一位进行排序。

快速排序和归并排序的区别是什么?

快速排序和归并排序都是常用的排序算法,它们的区别主要在于排序的方式和实现方法。快速排序的思路是选取一个基准元素,将待排序数组分成两部分,一部分小于基准元素,一部分大于基准元素,然后对这两部分分别进行递归排序,最后将排序好的两部分合并起来。具体实现时,可以选取第一个元素或者随机选取一个元素作为基准元素,然后使用双指针法将数组分成两部分,再递归排序这两部分。

归并排序的思路是将待排序数组分成若干个子数组,对每个子数组进行排序,然后将排好序的子数组合并起来,最终得到有序数组。具体实现时,可以使用递归将数组分成两部分,对每个子数组进行排序,然后使用归并操作将排好序的子数组合并起来。

快速排序的时间复杂度为O(nlogn),空间复杂度为O(logn),归并排序的时间复杂度也为O(nlogn),但空间复杂度为O(n)。因此,在空间有限的情况下,快速排序更适合使用;而在空间充足的情况下,归并排序更适合使用。

返回arr的最长无重复元素子数组的长度

可以使用滑动窗口的思想来解决这个问题。定义一个左指针left和一个右指针right,表示当前子数组的左右边界。遍历数组,每次将右指针向右移动一位,如果当前元素不在子数组中,则将右指针继续向右移动;如果当前元素在子数组中,则将左指针向右移动,直到子数组中不包含当前元素为止。每次移动右指针时,都记录当前子数组的长度,并更新最长无重复元素子数组的长度。最终返回最长无重复元素子数组的长度即可。

具体实现时,可以使用一个哈希表来记录每个元素最后一次出现的位置,如果当前元素在哈希表中已经存在,则更新左指针的位置为当前元素最后一次出现的位置加一。每次移动右指针时,都更新当前元素的最后一次出现位置。时间复杂度为O(n),空间复杂度为O(n)。以下是代码实现:

  1. function maxLength(arr) {
  2. let left = 0, right = 0, maxLen = 0;
  3. const map = new Map();
  4. while (right < arr.length) {
  5. if (map.has(arr[right]) && map.get(arr[right]) >= left) {
  6. left = map.get(arr[right]) + 1;
  7. }
  8. map.set(arr[right], right);
  9. maxLen = Math.max(maxLen, right - left + 1);
  10. right++;
  11. }
  12. return maxLen;
  13. }
'
运行

无重复最长子串

可以使用滑动窗口的思想来解决这个问题。定义一个左指针left和一个右指针right,表示当前子串的左右边界。遍历字符串,每次将右指针向右移动一位,如果当前字符不在子串中,则将右指针继续向右移动;如果当前字符在子串中,则将左指针向右移动,直到子串中不包含当前字符为止。每次移动右指针时,都记录当前子串的长度,并更新最长无重复字符子串的长度。最终返回最长无重复字符子串即可。

具体实现时,可以使用一个哈希表来记录每个字符最后一次出现的位置,如果当前字符在哈希表中已经存在,则更新左指针的位置为当前字符最后一次出现的位置加一。每次移动右指针时,都更新当前字符的最后一次出现位置。时间复杂度为O(n),空间复杂度为O(n)。以下是代码实现:

  1. function maxLength(str) {
  2. let left = 0, right = 0, maxLen = 0;
  3. const map = new Map();
  4. while (right < str.length) {
  5. if (map.has(str[right]) && map.get(str[right]) >= left) {
  6. left = map.get(str[right]) + 1;
  7. }
  8. map.set(str[right], right);
  9. maxLen = Math.max(maxLen, right - left + 1);
  10. right++;
  11. }
  12. return maxLen;
  13. }
'
运行

最长上升子序列

最长上升子序列问题是指在一个给定的序列中,找到一个最长的子序列,使得这个子序列中的元素是递增的。可以使用动态规划来解决这个问题。

定义一个数组dp,其中dp[i]表示以第i个元素为结尾的最长上升子序列的长度。初始时,dp数组中的每个元素都为1,因为每个元素本身都可以作为一个长度为1的上升子序列。

然后遍历整个序列,对于每个元素i,从0到i-1遍历之前的元素j,如果当前元素i大于元素j,说明可以将元素i加入到以元素j为结尾的最长上升子序列中,此时更新dp[i]的值为dp[j]+1。最终dp数组中的最大值即为最长上升子序列的长度。

时间复杂度为O(n^2),空间复杂度为O(n)。以下是代码实现:

  1. function lengthOfLIS(nums) {
  2. const n = nums.length;
  3. if (n === 0) {
  4. return 0;
  5. }
  6. const dp = new Array(n).fill(1);
  7. for (let i = 1; i < n; i++) {
  8. for (let j = 0; j < i; j++) {
  9. if (nums[i] > nums[j]) {
  10. dp[i] = Math.max(dp[i], dp[j] + 1);
  11. }
  12. }
  13. }
  14. return Math.max(...dp);
  15. }
'
运行

盛水最多的容器 

盛水最多的容器问题是指在一个给定的数组中,找到两个数,使得它们组成的容器可以盛最多的水。可以使用双指针来解决这个问题。

定义两个指针left和right,分别指向数组的左右两端。计算当前容器的容量,即min(height[left], height[right]) * (right - left),并更新最大容量maxArea的值。然后移动指针,如果height[left] < height[right],则left++,否则right--。重复上述步骤直到left >= right。

时间复杂度为O(n),空间复杂度为O(1)。以下是代码实现:

  1. function maxArea(height) {
  2. let left = 0;
  3. let right = height.length - 1;
  4. let maxArea = 0;
  5. while (left < right) {
  6. const area = Math.min(height[left], height[right]) * (right - left);
  7. maxArea = Math.max(maxArea, area);
  8. if (height[left] < height[right]) {
  9. left++;
  10. } else {
  11. right--;
  12. }
  13. }
  14. return maxArea;
  15. }
'
运行

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/947726
推荐阅读
相关标签
  

闽ICP备14008679号