一个算法题

There is an array generated by a rule:

  • The first item is 1.
  • If k is in the array, then k*3+1 and k*2+1 are in the array.
  • The array is sorted. There are no duplicate values.

Please write a function that accepts an input n . It should return the index n of the array.
For example [1, 3, 4, 7, 9, 10, 13, 15, 19, 21, 22, 27, ....]
n = 10 , return 22
n = 100 , return 447

// 起始数字为1,之后每个数字都是裂变成 两倍加1 和 三倍加1 两个数字。
const arr = [1];

let cache = [1]; // 减少裂变计算

const generate = (index) => {
    let stable = arr[index] && ((2 * cache[0] + 1) > arr[index]);

    while (arr.length < index + 1 || !stable) {
        console.log('计算');
        const newer = [];
        cache.forEach((n) => {
            const k2 = 2 * n + 1;
            const k3 = 3 * n + 1;
            // 不能有重复
            if (!arr.includes(k2) && !newer.includes(k2)) {
                newer.push(k2);
            }
            if (!arr.includes(k3) && !newer.includes(k3)) {
                newer.push(k3);
            }
        })

        arr.push(...newer); // 存入裂变出来的新数

        cache = newer.sort((a, b) => a - b); // 新数也是下一次裂变的基数
        arr.sort((a, b) => a - b); // 排序取目标值
        stable = !!arr[index] && ((2 * cache[0] + 1) > arr[index]); // 下一次裂变有可能产生比目标值更小的树,导致目标值改变
    }

    return arr[index];
}

console.log(generate(10));
console.log(generate(100));
console.log(generate(20));
console.log(arr);

暂记


2025.04.25 更新

现在AI越来越强了,看看AI写的 (DeepSeek)

1、使用 优先队列

function getNthElement(n) {
  const heap = [1]; // 维护一个优先队列(最小堆),用数组模拟
  const seen = new Set([1]); // 防止重复

  for (let i = 0; i < n; i++) {
    const current = heap[0]; // 当前最小值
    const candidates = [current * 3 + 1, current * 2 + 1];
    // 生成新元素并去重
    candidates.forEach(num => {
      if (seen.has(num)) return; // 去重
      seen.add(num);
      // 二分查找插入位置
      let left = 0, right = heap.length;
      while (left < right) {
        const mid = (left + right) >> 1;
        heap[mid] < num ? left = mid + 1 : right = mid;
      }
      heap.splice(left, 0, num);
    });
    heap.shift(); // 移除已处理的元素
  }
  return heap[0]; // 第n+1个元素是新的堆顶
}

用数组模拟了一个最小堆,用二分查找降低时间复杂度,挺好。
不过没有保留整个数组,处理完的元素都扔了,也就没有缓存,每次调用都重新生成一遍。

2、使用 双指针双序列

function findNthElement(n) {
  if (n === 0) return 1;

  const arr2 = [3];  // 存储2k+1的序列
  const arr3 = [4];  // 存储3k+1的序列
  let i2 = 0, i3 = 0;  // 双指针

  for (let i = 1; i < n; i++) {
    // 选择当前最小候选值
    let current;
    if (arr2[i2] < arr3[i3]) {
      current = arr2[i2];
      i2++;
    } else if (arr3[i3] < arr2[i2]) {
      current = arr3[i3];
      i3++;
    } else {  // 处理相等的情况
      current = arr2[i2];
      i2++;
      i3++;
    }
    // 生成新候选值(保证严格递增)
    arr2.push(current * 2 + 1);
    arr3.push(current * 3 + 1);
  }

  // 返回第n个元素
  return Math.min(arr2[i2], arr3[i3]);
}

console.log(findNthElement(10)); // 22
console.log(findNthElement(100)); // 447

这应该是最快的实现方式了,时间复杂度只有 O(n)
不过,通用性就不太行了,如果每个数是裂变成3个或更多,就得准备更多序列和指针,比较大小时也麻烦了。
通用性还是 最小堆的算法最通用,如果是裂变成多个数也能用,就算不是 2k+1 这种简单裂变规则也都能用。

继续优化
双指针法的实现里,指针的移动,以及 两个序列数组的 push 都可以优化

function findNthElement(n) {
  if (n === 0) return 1;

  const arr2 = Array(n); // 预留空间 避免频繁的push数组扩容 消耗性能
  const arr3 = Array(n);
  arr2[0] = 3;
  arr3[0] = 4;
  let i2 = 0, i3 = 0;  // 双指针
  let index = 1; // 当前处理到了第几个元素
  while (index < n) {
    // 选择当前最小候选值 current就是每一个满足要求的元素
    const curr2 = arr2[i2], curr3 = arr3[i3];
    const current = curr2 <= curr3 ? curr2 : curr3;
    // 生成新候选值(保证严格递增) 可见arr2 arr3 两数组长度永远是相等的
    arr2[index] = current * 2 + 1; // 直接索引赋值,比push更快
    arr3[index] = current * 3 + 1;
    index++;
    // 移动指针 如果相等的情况,两个指针都要移动
    i2 += (current === curr2);
    i3 += (current === curr3);    
  }

  // 返回第n个元素
  return Math.min(arr2[i2], arr3[i3]);
}

如果要做缓存,避免每次重新生成,也只需要把每次的 current 收集好就行

// 最小堆算法的 加缓存
const getNthElement = (() => {
  const res = [1]; // 结果数组 闭包 缓存
  const heap = [1]; // 维护一个优先队列(最小堆),用数组模拟
  let index = 0; // 当前处理到了第几个元素

  return (n) => {
    if (res.length > n) return res[n]; // 直接返回缓存

    res.length = n + 1; // 预留空间 以避免频繁的push数组扩容 消耗性能
    while (index < n) {
      const current = heap.shift(); // 弹出最小值,用来裂变
      const candidates = [current * 3 + 1, current * 2 + 1]; // 生成新元素
      // 插入新元素,维护最小堆,保证递增且不重复
      candidates.forEach((num) => {
        let left = 0; // 二分查找插入位置
        let right = heap.length;
        while (left < right) {
          const mid = (left + right) >> 1;
          heap[mid] < num ? (left = mid + 1) : (right = mid);
        }
        if (heap[left] === num) return; // 去重 不需要用Set
        heap.splice(left, 0, num);
      });

      index++; // 索引同步递增
      res[index] = heap[0]; // 此时堆顶元素已确定是下一个结果,放入结果数组
    }
    return res[n];
  };
})();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值