JavaScript数据结构和相关算法整合

1.两数之和

问题:给定一个数组nums和一个目标值target,在该数组中找出和为目标值的两个数

在JavaScript中,你可以通过遍历数组并使用一个对象(在JavaScript中通常使用对象作为哈希表)来存储已经遍历过的数字及其索引,以找出和为特定目标值的两个数。以下是一个可能的实现:

// 1.两数之和-给定一个数组 nums 和一个目标值 target,在该数组中找出和为目标值的两个数
function twoSum(nums, target) {
  // 创建一个空对象来存储数组中的数字及其索引
  const numMap = {}; // 遍历数组
  for (let i = 0; i < nums.length; i++) {
    const num = nums[i];
    const complement = target - num; // 计算补数 // 检查补数是否已经在哈希表中
    if (complement in numMap) {
      // 如果在,返回结果
      return [numMap[complement], i];
    } // 如果不在,将当前数字及其索引添加到哈希表中
    numMap[num] = i;
  } // 如果没有找到符合条件的两个数,抛出一个错误
  throw new Error("No two sum solution");
}
// 示例
const nums = [2, 3, 6, 15, 7];
const target = 9;
try {
  const result = twoSum(nums, target);
  console.log(result); // 输出: [1, 2]
} catch (error) {
  console.error(error.message);
}

在这个实现中,我们首先创建了一个空对象numMap。然后,我们遍历数组nums中的每个元素。对于每个元素,我们计算目标值与当前元素的差值(补数)。然后,我们检查这个补数是否已经在 numMap中。如果在,说明我们已经找到了两个数的和为目标值,我们返回这两个数的索引。如果不在,我们将当前元素及其索引添加到numMap中,以便后续的检查。

如果遍历完整个数组都没有找到符合条件的两个数,我们抛出一个错误。在实际应用中,你可能希望返回一个特殊的值(如nullundefined),而不是抛出错误,这取决于你的具体需求。

2.三数之和

问题:给定一个数组nums,判断nums中是否存在三个元素abc,使得 a + b + c = target,找出所有满足条件且不重复的三元组合

你可以使用两个嵌套的循环来遍历数组,然后对于每一对元素,使用第三个循环来查找第三个元素,使得三者的和等于目标值。但是,这种方法的时间复杂度为O(n^3),对于大数组来说可能效率不高。

一个更高效的方法是使用哈希表(在JavaScript中,我们通常使用对象作为哈希表)来存储数组中的元素,使得我们可以在O(1)的时间内查找某个元素是否存在。然后,我们可以使用两个指针来遍历数组,同时更新哈希表。这种方法的时间复杂度为O(n^2)

function threeSum(nums, target) {
  const result = [];
  nums.sort((a, b) => a - b); // 先对数组进行排序
  const map = {};
  for (let i = 0; i < nums.length - 2; i++) {
    // 跳过重复元素
    if (i > 0 && nums[i] === nums[i - 1]) continue;
    let left = i + 1;
    let right = nums.length - 1;
    while (left < right) {
      const sum = nums[i] + nums[left] + nums[right];
      if (sum === target) {
        result.push([nums[i], nums[left], nums[right]]); // 跳过重复元素
        while (left < right && nums[left] === nums[left + 1]) left++;
        while (left < right && nums[right] === nums[right - 1]) right--;
        left++;
        right--;
      } else if (sum < target) {
        left++;
      } else {
        right--;
      }
    }
  }
  return result;
}

const nums = [2, 3, 6, 7, 15, 10, 7];
const target = 15;

const result = threeSum(nums, target);
console.log(result); // 输出: [[2, 3, 10],[2 , 6, 7]]

这个函数的输入是一个数组nums和一个目标值target,输出是一个二维数组,其中每个子数组都是一个满足条件的三元组合。这个函数首先对数组进行排序,然后使用两个指针leftright来遍历数组,同时更新哈希表。当找到一个满足条件的三元组合时,将其添加到结果数组中,并跳过所有重复的元素。最后,返回结果数组。

3.找不重复字符

问题:输入一个字符串,找到第一个不重复字符的下标

要找到字符串中第一个不重复字符的下标,你可以遍历字符串并使用一个对象(在JavaScript中作为哈希表使用)来记录每个字符出现的次数。然后再次遍历字符串,找到第一个出现次数为1的字符并返回其下标。以下是实现这一功能的JavaScript代码示例:

function firstNonRepeatingChar(str) {
  // 使用对象来记录每个字符出现的次数
  const charCount = {};
  for (let i = 0; i < str.length; i++) {
    const char = str[i]; // 如果字符已经存在于对象中,则增加计数
    if (charCount[char]) {
      charCount[char]++;
    } else {
      // 否则,初始化计数为1
      charCount[char] = 1;
    }
  } // 再次遍历字符串,找到第一个出现次数为1的字符
  for (let i = 0; i < str.length; i++) {
    const char = str[i];
    if (charCount[char] === 1) {
      return i; // 返回第一个不重复字符的下标
    }
  } // 如果没有找到不重复字符,则返回-1
  return -1;
}
// 示例用法
const inputString = "abcabbb";
const index = firstNonRepeatingChar(inputString);
console.log(index); // 输出: 2(因为字符'c'是第一个只出现一次的字符,其下标为2)

4.输入一个字符串,打印出该字符串中,所有字符的排列组合

function getPermutations(str) {
  const result = [];
  function permute(current, remaining) {
    if (remaining.length === 0) {
      result.push(current);
    } else {
      for (let i = 0; i < remaining.length; i++) {
        // 每次都拿出来一个放在最前面
        const next = current + remaining[i];
        // 删除被拿出来的那个,得到新字符串
        const remainingChars = remaining.slice(0, i) + remaining.slice(i + 1);
        // 新字符串递归
        permute(next, remainingChars);
      }
    }
  }

  permute("", str);
  // 去重
  return [...new Set(result)];
}

// 示例用法
const input1 = "abb";
const input2 = "abc";

console.log(getPermutations(input1)); //[ 'abb', 'bab', 'bba' ]
console.log(getPermutations(input2)); //[ 'abc', 'acb', 'bac', 'bca', 'cab', 'cba' ]

5.冒泡排序

冒泡排序是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。

function bubbleSort(arr) {
  for (let i = 0; i < arr.length-1; i++) {
    for (let j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        let temp = arr[j + 1];
        arr[j + 1] = arr[j];
        arr[j] = temp;
      }
    }
  }
  return arr;
}
// 示例
var arr = [34, 8, 64, 51, 32, 21];
console.log(bubbleSort(arr)); // 输出:[8, 21, 32, 34, 51, 64]

6.选择排序

选择排序是一种简单直观的排序算法。它的工作原理是首先在未排序序列中找到最小(或最大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(或最大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

function selectionSort(arr) {
  for (let i = 0; i < arr.length-1; i++) {
    let minIndex = i;
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[j] < arr[minIndex]) {
        minIndex = j;
      }
    }
    let temp = arr[minIndex];
    arr[minIndex] = arr[i];
    arr[i] = temp;
  }
  return arr;
}
// 示例
let arr = [64, 25, 12, 22, 11];
console.log(selectionSort(arr)); // 输出:[11, 12, 22, 25, 64]

7.快速排序

快速排序是一种高效的排序算法,它使用了分治法的策略。在快速排序中,我们选择一个“基准”元素,然后将数组分为两部分:一部分包含所有比基准小的元素,另一部分包含所有比基准大的元素。这个过程递归地应用于子数组,直到整个数组被排序。

function quickSort(arr) {
  if (arr.length < 2) {
    return arr;
  }
  var pivotIndex = Math.floor(arr.length / 2);
  var pivot = arr.splice(pivotIndex, 1)[0];
  var left = [];
  var right = [];
  for (var i = 0; i < arr.length; i++) {
    if (arr[i] < pivot) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return quickSort(left).concat([pivot], quickSort(right));
}
// 示例
var arr = [3, 6, 8, 10, 1, 2, 1];
console.log(quickSort(arr)); // 输出:[1, 1, 2, 3, 6, 8, 10]

在这个实现中,我们首先检查数组的长度,如果长度小于2,那么数组已经是有序的,直接返回。然后,我们选择数组中间的元素作为基准(pivot),并从数组中移除它。接着,我们遍历剩余的数组元素,将它们分别放入比基准小(left)和比基准大(right)的两个数组中。最后,我们递归地对这两个子数组进行快速排序,并将排序后的子数组与基准合并,得到最终的排序结果。

请注意,这个实现为了简单起见,使用了 splice 方法来移除基准元素,这可能会影响原数组。在实际应用中,你可能希望避免修改原数组,或者使用其他方法来选取和移除基准元素。此外,快速排序的性能会受到基准选择的影响,实际应用中可能会采用更复杂的策略来选择基准,如随机选择或三数取中等。

8.插入排序

插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。

function insertionSort(arr) {
  var len = arr.length;
  var preIndex, current;
  for (var i = 1; i < len; i++) {
    preIndex = i - 1;
    current = arr[i];
    while (preIndex >= 0 && arr[preIndex] > current) {
      arr[preIndex + 1] = arr[preIndex];
      preIndex--;
    }
    arr[preIndex + 1] = current;
  }
  return arr;
}
// 示例
var arr = [4, 3, 2, 10, 12, 1, 5, 6];
console.log(insertionSort(arr)); // 输出:[1, 2, 3, 4, 5, 6, 10, 12]

在这个代码中,我们遍历数组中的每个元素,从第二个元素开始(索引为1)。对于每个元素,我们将其保存到一个临时变量current中,然后向前扫描已排序的序列,找到当前元素应该插入的位置。在向前扫描的过程中,我们将比当前元素大的元素向后移动一位。最后,将当前元素插入到正确的位置。这个过程对数组中的每个元素重复,直到整个数组排序完成。

9.列表转成树

在JavaScript中,将列表(数组)转换为树形结构是一个常见的任务。通常,列表中的每个对象都有一个表示其父级对象的字段(例如,parentId)。以下是一个简单的示例,展示如何实现这种转换:

function listToTree(list, parentId = null) {  
    let map = {};  
    let node, roots = [];  
    let i;  
    for (i = 0; i < list.length; i += 1) {  
        map[list[i].id] = { ...list[i], children: [] };  
    }  
    for (i = 0; i < list.length; i += 1) {  
        node = map[list[i].id];  
        if (list[i].parentId === parentId) {  
            roots.push(node);  
        } else {  
            map[list[i].parentId].children.push(node);  
        }  
    }  
    return roots;  
}  
  
// 示例数据  
const list = [  
    { id: 1, name: 'Node 1', parentId: null },  
    { id: 2, name: 'Node 1.1', parentId: 1 },  
    { id: 3, name: 'Node 1.2', parentId: 1 },  
    { id: 4, name: 'Node 2', parentId: null },  
    { id: 5, name: 'Node 2.1', parentId: 4 },  
    { id: 6, name: 'Node 2.2', parentId: 4 },  
    { id: 7, name: 'Node 2.1.1', parentId: 5 },  
];  
  
// 转换列表为树  
const tree = listToTree(list);  
  
// 打印树形结构  
function printTree(tree, level = 0) {  
    tree.forEach(node => {  
        console.log(`${'--'.repeat(level)} ${node.name}`);  
        if (node.children.length > 0) {  
            printTree(node.children, level + 1);  
        }  
    });  
}  
  
printTree(tree);

在这个示例中,listToTree函数接受一个列表(list)和一个可选的父级IDparentId)。它首先创建一个映射(map),将每个列表项与其ID关联起来,并初始化一个空的children数组。然后,它遍历列表,将每个项添加到其父项的children数组中,或者如果它没有父项(即它是根节点),则将其添加到roots数组中。最后,它返回根节点的数组。

printTree函数是一个辅助函数,用于以易于阅读的格式打印树形结构。它递归地遍历树,并在每个级别前打印相应数量的破折号(--),以表示节点的深度。

10.深度优先遍历

对树进行遍历,从第一个节点开始,遍历其子节点,直到它的所有子节点都被遍历完毕,然后再遍历它的兄弟节点。

在JavaScript中,深度优先遍历(Depth-First Search, DFS)是一种用于遍历或搜索树或图的算法。这个算法会尽可能深地搜索树的分支。当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。

以下是一个简单的JavaScript函数,它实现了对树的深度优先遍历。这个函数假设树是由节点对象组成的,每个节点对象都有一个children属性,该属性是一个包含该节点的所有子节点的数组。

function depthFirstSearch(root) {
  if (root === null) {
    return;
  }
  console.log(root.value); // 访问当前节点 // 遍历当前节点的所有子节点
  for (let i = 0; i < root.children.length; i++) {
    depthFirstSearch(root.children[i]);
  }
}
// 示例树结构
const tree = {
  value: "root",
  children: [
    {
      value: "child1",
      children: [
        { value: "grandchild1", children: [] },
        { value: "grandchild2", children: [] },
      ],
    },
    {
      value: "child2",
      children: [
        { value: "grandchild3", children: [] },
        { value: "grandchild4", children: [] },
      ],
    },
  ],
};
// 从根节点开始深度优先遍历
depthFirstSearch(tree); //root child1 grandchild1 grandchild2 child2 grandchild3 grandchild4

在这个例子中,depthFirstSearch函数首先检查传入的节点是否为null。如果是,函数就返回并不做任何事情。否则,它会打印出当前节点的值,然后遍历并递归调用其所有子节点。这样就保证了每个节点都会被访问,并且是先访问其所有子节点,然后再访问其兄弟节点。

11.广度优先遍历

在JavaScript中,广度优先遍历(Breadth-First Search, BFS)是一种用于遍历或搜索树或图的算法。这种算法从根节点(或任意节点)开始,访问最靠近根节点的节点。广度优先遍历使用队列数据结构来存储待访问的节点。

function breadthFirstSearch(root) {
  if (root === null) {
    return;
  } // 使用队列来进行广度优先遍历
  const queue = [root];
  while (queue.length > 0) {
    const currentNode = queue.shift(); // 取出队列中的第一个节点
    console.log(currentNode.value); // 访问当前节点的值 // 将当前节点的所有子节点加入队列
    for (const child of currentNode.children) {
      queue.push(child);
    }
  }
}
// 示例树结构
const tree = {
  value: "root",
  children: [
    {
      value: "child1",
      children: [
        { value: "grandchild1", children: [] },
        { value: "grandchild2", children: [] },
      ],
    },
    {
      value: "child2",
      children: [
        { value: "grandchild3", children: [] },
        { value: "grandchild4", children: [] },
      ],
    },
  ],
};
// 从根节点开始广度优先遍历
breadthFirstSearch(tree); //root child1 child2 grandchild1 grandchild2 grandchild3 grandchild4

在这个例子中,breadthFirstSearch函数首先检查传入的根节点是否为null。如果不是,它将根节点放入队列中。然后,它进入一个循环,只要队列不为空,就持续执行以下操作:

  • 从队列中取出第一个节点(即最早入队的节点)。
  • 访问(在这里是打印)该节点的值。
  • 将该节点的所有子节点加入队列。

这个过程将确保树的遍历按照广度优先的方式进行,即先遍历所有兄弟节点,再遍历子节点,一层层向下遍历。

12.查找树形结构中符合要求的节点

在JavaScript中,查找树形结构中符合特定要求的节点通常涉及递归遍历树的所有节点,并检查每个节点的属性是否满足条件。以下是一个示例函数,它接受一个树形结构和一个回调函数作为参数,回调函数定义了节点必须满足的条件。函数会返回所有满足条件的节点数组。

function findNodes(tree, predicate) {
  const result = []; // 用于存储符合条件的节点
  function traverse(node) {
    if (predicate(node)) {
      result.push(node); // 如果节点满足条件,将其添加到结果数组
    }
    for (const child of node.children) {
      traverse(child); // 递归遍历子节点
    }
  }
  traverse(tree); // 从根节点开始遍历
  return result;
}
// 示例树结构
const tree = {
  value: "root",
  children: [
    {
      value: "child1",
      children: [
        { value: "grandchild1", children: [] },
        { value: "grandchild2", children: [] },
      ],
    },
    {
      value: "child2",
      children: [
        { value: "grandchild3", children: [] },
        { value: "grandchild4", children: [] },
      ],
    },
  ],
};
// 示例:查找所有值为'grandchild'的节点
const predicate = (node) => node.value.startsWith("grandchild");
const matchingNodes = findNodes(tree, predicate);
console.log(matchingNodes);
//   [
//     { value: 'grandchild1', children: [] },
//     { value: 'grandchild2', children: [] },
//     { value: 'grandchild3', children: [] },
//     { value: 'grandchild4', children: [] }
//   ]

在这个例子中,findNodes函数接受一个树形结构tree和一个回调函数predicatepredicate 函数应该接受一个节点作为参数,并返回一个布尔值,表示该节点是否满足搜索条件。findNodes函数通过递归调用traverse函数来遍历树的每个节点,并在遇到满足条件的节点时将其添加到结果数组中。最后,函数返回所有满足条件的节点数组。

你可以根据需要调整predicate函数来定义不同的搜索条件。例如,你可以查找具有特定属性的节点,或者基于节点的值或其他属性进行更复杂的匹配。

13.二叉查找树

问题:判断一个数组,是否为某二叉查找树的前序遍历结果,二叉查找树特点是所有的左节点比父节点的值小,所有的右节点比父节点的值大

要判断一个数组是否为二叉查找树的前序遍历结果,我们可以利用二叉查找树的特性:所有左子节点的值都小于父节点,所有右子节点的值都大于父节点。在前序遍历中,遍历的顺序是根节点、左子树、右子树。

function verifyPreorder(preorder) {
  if (preorder.length === 0) {
    return true; // 空数组是有效的前序遍历结果
  }
  const rootVal = preorder[0];
  const leftSubtree = [];
  const rightSubtree = []; // 分离左子树和右子树
  for (let i = 1; i < preorder.length; i++) {
    if (preorder[i] < rootVal) {
      leftSubtree.push(preorder[i]);
    } else {
      rightSubtree.push(preorder[i]);
    }
  } // 递归验证左子树和右子树
  return (
    verifyPreorder(leftSubtree) && // 左子树必须是有效的二叉查找树前序遍历
    verifyPreorder(rightSubtree) && // 右子树必须是有效的二叉查找树前序遍历 // 右子树中的所有元素必须都大于根节点的值
    rightSubtree.every((val) => val > rootVal)
  );
}
// 示例
const preorderTraversal = [8, 5, 1, 7, 10, 12];
console.log(verifyPreorder(preorderTraversal));

这个算法的思路是:

  • 如果数组为空,那么它肯定是一个有效的二叉查找树的前序遍历结果。
  • 否则,我们取数组的第一个元素作为根节点的值。
  • 遍历数组中剩余的元素,将它们分为左子树和右子树两部分。左子树中的元素值都小于根节点,右子树中的元素值都大于或等于根节点。
  • 递归地对左子树和右子树进行验证,确保它们也是有效的二叉查找树的前序遍历结果。
  • 同时,验证右子树中的所有元素是否都大于根节点的值。

请注意,这个算法假设输入的数组不包含重复的元素。如果允许重复元素,那么前序遍历的结果可能无法唯一确定一棵二叉查找树,因此需要根据具体问题的需求进行相应调整。

14.买卖股票问题

问题:给定一个整数数组,其中第 i 个元素代表了第 i天的股票价格;非负整数 fee 代表了交易股票的手续费用,求返回获得利润的最大值。

这个问题可以使用动态规划来解决。我们可以定义两个变量,分别表示手上持有股票时的最大利润和不持有股票时的最大利润。对于每一天,我们都有两种选择:买入股票或者卖出股票。

function maxProfit(prices, fee) {  
    if (prices.length <= 1) {  
        return 0;  
    }  
      
    let cash = 0; // 不持有股票时的最大利润  
    let hold = -prices[0]; // 持有股票时的最大利润,初始化为第一天的股票价格取反,表示买入股票  
      
    for (let i = 1; i < prices.length; i++) {  
        // 如果今天卖出股票,则利润为之前的现金加上今天的股票价格  
        cash = Math.max(cash, hold + prices[i] - fee);  
        // 如果今天买入股票,则利润为之前的持有股票利润减去今天的股票价格  
        hold = Math.max(hold, cash - prices[i]);  
    }  
      
    return cash;  
}  
  
// 示例  
const prices = [1, 3, 2, 8, 4, 9];  
const fee = 2;  
console.log(maxProfit(prices, fee)); // 输出 8

在这个例子中,我们在第2天买入股票(价格为3),在第3天卖出(价格为2),需要支付手续费2,第5天买入(价格为4),第6天卖出(价格为9),同样需要支付手续费2,所以总利润为8。

注意,我们每次卖出股票时都要支付手续费,这会影响我们的总利润。这个算法的时间复杂度是O(n),其中n是数组的长度。

15.斐波那契数列

问题:从第3项开始,当前项等于前两项之和: 1 1 2 3 5 8 13 21 ……,计算第n项的值

在JavaScript中,可以使用递归或迭代的方式来实现斐波那契数列。由于递归的效率较低,特别是当n较大时,会导致大量的重复计算,因此在这里我将展示迭代的方式来实现斐波那契数列。

以下是一个使用迭代方式计算斐波那契数列第n项值的JavaScript函数:

function fibonacci(n) {
  if (n <= 0) {
    return "输入错误,n必须为正整数";
  } else if (n === 1 || n === 2) {
    return 1;
  } else {
    let a = 1,
      b = 1,
      temp;
    for (let i = 3; i <= n; i++) {
      temp = a + b;
      a = b;
      b = temp;
    }
    return b;
  }
}
// 测试函数
console.log(fibonacci(10)); // 输出第10项的值 55

这个函数首先检查n是否小于等于0,如果是,则返回错误信息。然后,它检查n是否等于1或2,因为斐波那契数列的前两项都是1。如果n大于2,那么函数就会进入一个循环,从第三项开始计算斐波那契数列的值,直到达到第n项。在循环中,我们使用变量a和b来存储前两项的值,并使用temp来存储当前项的值。在每次循环中,我们都会更新a、b和temp的值,以便计算下一项的值。最后,函数返回第n项的值。

16.最长递增子序列

问题:一个整数数组 nums,找到其中一组最长递增子序列的值

在JavaScript中,最长递增子序列(Longest Increasing Subsequence,简称LIS)问题可以使用动态规划(Dynamic Programming)来解决。下面是一个示例实现:

function longestIncreasingSubsequence(nums) {  
    if (nums.length === 0) return [];  
  
    // 初始化一个数组来存储每个位置的最长递增子序列的长度  
    let lengths = new Array(nums.length).fill(1);  
    // 初始化一个数组来存储每个位置的最长递增子序列的前一个元素的索引  
    let prevIndices = new Array(nums.length).fill(-1);  
  
    let maxLength = 1; // 最长递增子序列的初始长度  
    let endIndex = 0; // 最长递增子序列的最后一个元素的索引  
  
    // 遍历数组,计算每个位置的最长递增子序列长度和前一个元素的索引  
    for (let i = 1; i < nums.length; i++) {  
        for (let j = 0; j < i; j++) {  
            if (nums[i] > nums[j] && lengths[i] < lengths[j] + 1) {  
                lengths[i] = lengths[j] + 1;  
                prevIndices[i] = j;  
            }  
        }  
        // 更新最长递增子序列的长度和最后一个元素的索引  
        if (maxLength < lengths[i]) {  
            maxLength = lengths[i];  
            endIndex = i;  
        }  
    }  
  
    // 根据最后一个元素的索引和前一个元素的索引数组,构建最长递增子序列  
    let lis = [];  
    while (endIndex !== -1) {  
        lis.unshift(nums[endIndex]);  
        endIndex = prevIndices[endIndex];  
    }  
  
    return lis;  
}  
  
// 示例  
const nums = [10, 9, 2, 5, 3, 7, 101, 18];  
const lis = longestIncreasingSubsequence(nums);  
console.log(lis); // 输出: [2, 3, 7, 101] 或者其他可能的递增子序列,如 [2, 5, 7, 101] 等

在这个实现中,lengths数组用于存储以每个位置为结尾的最长递增子序列的长度,而prevIndices数组则用于存储每个位置的最长递增子序列中前一个元素的索引。我们通过两次遍历来构建这两个数组,并在遍历过程中更新最长递增子序列的长度和最后一个元素的索引。

最后,我们根据 endIndexprevIndices 数组回溯构建出最长递增子序列,并返回结果。

需要注意的是,最长递增子序列可能不唯一,因此上面的代码可能输出不同的递增子序列,但它们的长度都是相同的,即数组 nums 的最长递增子序列的长度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值