如何用快速排序解算法题

160 阅读5分钟

文章简要概述

  • 本文主要进行快速排序相关的算法题刷题题解记录,记录快速排序相关算法以及如何解。
  • 这文一共有3道题,主要介绍leetcode中347. 前 K 个高频元素973. 最接近原点的 K 个点、和451. 根据字符出现频率排序的解题思路。

与快速排序相关算法

347. 前 K 个高频元素

347. 前 K 个高频元素--leetcode

题目大意:

给你一个整数数组 `nums` 和一个整数 `k` ,请你返回其中出现频率前 `k` 高的元素。你可以按 **任意顺序** 返回答案。

示例

输入: nums = [1,1,1,2,2,3], k = 2

输出: [1,2]

解题思路:

  • 将数组分成已排序和待排序
  • 看已排序的小于k,证明数不够,从后面拿过来
  • 同理,看已排序的大于k,证明排好的数已经多了,从前面拿过来。

代码:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var topKFrequent = function(nums, k) 
     // 把 list[right] 当成是 value
     // 把大于等于 value 的数字放到左边 
     // 把小于 value 的数字放到右边
    function partition(list, left, right, comparator) {
      if (typeof comparator != 'function') {
          comparator = function(item, value) {
              return item >= value;
          }
      }
      let i = left;
      for (let j = left; j < right; j++) {
          if (comparator(list[j], list[right])) {
              [list[i], list[j]] = [list[j], list[i]];
              i++;
          }
      }
      [list[i], list[right]] = [list[right], list[i]];
      return i;
    }
    // 寻找第 k 大元素
    // 如果 value 的下标刚好是 k - 1,那我们就找到了
    // 如果下标大于 k - 1,那就在 [left, index - 1] 这段找第 k 大元素
    // 如果下标小于 k - 1,那就对 [pivotIndex + 1, right] 这段找第 k - pivotIndex + left - 1 大元素
    function quickSelect(list, k, left = 0, right = list.length - 1, comparator) {
       if (left >= right) return list[left];
       const index = partition(list, left, right, comparator);
       if (index - left === k - 1) return list[index];
       else if (index - left > k - 1) {
           return quickSelect(list, k, left, index - 1, comparator);
       } else {
          return quickSelect(list, k - index + left - 1, index + 1, right, comparator);
       }
    }
    // 统计出现次数
    const map = {};
    for(const key of nums) {
       key in map || (map[key] = 0);
       map[key]++;
    }
    const list = Object.entries(map);
    quickSelect(list, k, 0, list.length - 1, function(item, value) {
       return item[1] >= value[1];
    })
    return list.slice(0, k).map(item => item[0]);
};

973. 最接近原点的 K 个点

973. 最接近原点的 K 个点--leetcode

题目大意:

给定一个数组 `points` ,其中 `points[i] = [xi, yi]` 表示 **X-Y** 平面上的一个点,并且是一个整数 `k` ,返回离原点 `(0,0)` 最近的 `k` 个点。

这里,平面上两点之间的距离是 **欧几里德距离**( `√(x1 - x2)2 + (y1 - y2)2` )。

你可以按 **任何顺序** 返回答案。除了点坐标的顺序之外,答案 **确保** 是 **唯一** 的。

示例:

closestplane1.jpg

输入:points = [[1,3],[-2,2]], k = 1

输出:[[-2,2]]

解释:

(1, 3) 和原点之间的距离为 sqrt(10),

(-2, 2) 和原点之间的距离为 sqrt(8),

由于 sqrt(8) < sqrt(10),(-2, 2) 离原点更近。

我们只需要距离原点最近的 K = 1 个点,所以答案就是 [[-2,2]]。

解题思路:

  • 先根据勾股公式求出两点的距离,利用sort进行排序
  • 截取前k个数据

代码:

/**
 * @param {number[][]} points
 * @param {number} k
 * @return {number[][]}
 */
var kClosest = function(points, k) {
   return points.sort((a,b) => a[0]*a[0] - b[0]*b[0] + a[1]*a[1] - b[1]*b[1]).slice(0,k)
};

// 快排
var kClosest = function (points, K) {
    if (points.length <= K) {
        return points;
    }
    quickSelect(points, 0, points.length - 1, K); // 范围是整个数组
    return points.slice(0, K);  // 排完后,取前K个
};

function quickSelect(points, start, end, K) {
    const pivot = distance(points[start]);
    let l = start, r = end;
    while (l <= r) { 	     // 左右两个指针
        if (distance(points[l]) <= pivot) { // 左指针指向的元素比pivot小,没毛病,看下一个,指针右移
            l++;
            continue;
        }
        if (distance(points[r]) > pivot) { // 右指针指向的元素比pivot大,没毛病,看下一个,指针左移
            r--;
            continue;
        }
        // 左指针指向的元素比pivot大,右指针指向的元素比pivot小,交换左右指针指向的元素
        [points[l], points[r]] = [points[r], points[l]];
        l++;
        r--;        // 指针同时收缩1
    }
    [points[start], points[r]] = [points[r], points[start]]; // 交换pivot元素和右指针指向的元素
    if (r == K) { // 排好了
        return;
    } else if (r < K) { // 左边还不够K个,则[r+1:end]要继续排
        quickSelect(points, r + 1, end, K);
    } else { // 左边大于K个,则对左边继续排
        quickSelect(points, start, r - 1, K);
    }
}

function distance(point) {  // 求point到原点的距离
    return point[0] * point[0] + point[1] * point[1];
}

451. 根据字符出现频率排序

451. 根据字符出现频率排序--leetcode

题目大意:

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例:

输入: "tree"

输出: "eert"

解释:

'e'出现两次,'r'和't'都只出现一次。

因此'e'必须出现在'r'和't'之前。此外,"eetr"也是一个有效的答案。

解题思路:

  • 先遍历字符串,统计每一个字符出现的次数
  • 将统计的字符次数进行排序(这里借助了sort)
  • 按照统计的次数按照从大到小遍历,记录对应的字符
  • 返回记录后的字符串顺序

代码:

/**
 * @param {string} s
 * @return {string}
 */
var frequencySort = function(s) {
   const map = new Map();
   const len = s.length;
   for(let i = 0; i < len; i++) {
     const key = s[i];
     let count = (map.get(key) || 0) + 1;
     map.set(key, count);
   }
   const list = [...map.keys()];
   list.sort((a,b) => map.get(b) - map.get(a));
   const size = list.length;
   const res = [];
   for(let i = 0; i < size; i++) {
      const k = list[i];
      const count = map.get(k);
      for(j = 0; j < count; j++) {
         res.push(k);
      }
   }
   return res.join('')
};

结束语

数据结构与算法相关的练习题会持续输出,一起来学习,持续关注。当前是快速排序部分。后期还会有其他类型的数据结构,题目来源于leetcode。

往期文章:

树形结构-对应算法详解一                             树形结构-对应算法详解二                             链表相关算法复习一

链表相关算法复习二                                      链表相关算法复习三                                      数据结构与算法-栈一

数据结构与算法-栈二                                    数据结构与算法-队列一                                数据结构与算法-队列二

数据结构与算法-链表一                                 数据结构与算法-链表二                                 数据结构与算法-链表三

有兴趣的可以一起来刷题,求点赞👍 关注!