如何构建LeetCode的两数之和问题的可视化 – HTML,CSS和JavaScript项目

随着当前就业市场的形势,许多人都在刻苦学习LeetCode [https//leetcode.com/]来准备技术面试但有时候,如果有一个可视化展示这些问题背后的算法,会更好在这篇教程中,我们会介绍一些相关的算法

随着就业市场的现状,有很多人在为了准备技术面试而努力解决 LeetCode 的问题。

但有时候,如果能有一个显示这些问题背后算法的可视化,那就太好了。

在本教程中,我们将构建一个 可视化,展示了解一个名为 两数之和 的热门LeetCode问题的几种方法。我们将使用纯HTML、CSS和JavaScript构建这个项目。

目录

前提条件

本教程假设您具备HTML、CSS和JavaScript的基本知识。如果您还没有接触过这些语言的初学课程,我建议您从以下资源开始:

本教程还假设您对如何使用代码编辑器或集成开发环境有一些基本知识。如果没有的话,我建议您查阅以下资源:

项目设置

您可以选择在您喜欢的本地代码编辑器中或通过在线IDE或编辑器如 CodePenCodeSandboxReplit 构建此项目。

这个项目将由三个文件组成:index.htmlindex.jsstyles.css。由于这个项目主要关注于 JavaScript,我在这个 GitHub 仓库中提供了所有的 HTML 和 CSS 代码。

你可以自由地forkclone这个仓库,或者将 HTML 和 CSS 文件中的代码复制到你的项目中。

一旦你设置好项目环境,然后你应该启动本地服务器并在屏幕上看到这个结果:

Screenshot-2023-11-19-at-4.18.24-PM
力扣 Two Sum 问题的可视化样式

如何解决力扣的 Two Sum 问题

在我们构建这个问题的可视化之前,我们首先需要了解和解决这个问题。

问题描述

对于这个问题,你将得到一个任意排序的数字列表和一个目标数字。目标是找到一对数字,使它们的和等于目标,并返回该数字对的索引数组。

在这个例子中,我们有以下列表和目标数字:

[2,7,11,15],目标:9

数字 2 和 7 相加等于 9,因此我们应该返回 [0,1],因为这表示数字对在数组中的索引位置。

对于这个问题,你可以假设数组中至少有两个或更多的数字,并且只有一个可能的解决方法。

所以,举个例子,你不能有以下输入,因为它没有解决方案,因为列表中没有两个数字的和等于目标:

[1,2,3,4,5],目标:55

你也不会得到有多个解的输入。以下输入有两个答案 [0,1][1,2],这与问题的规则不符。

[3,3,3],目标:6

蛮力法

更直观的方法是从数字列表的开头开始,比较每一对可能的数字,直到找到它们的和等于目标。

让我们看一个例子:

[11, 15, 2, 7],目标:9

我们可以从列表中的第一个数字(11)开始,检查每一对可能的数字并查看它们是否总和等于目标数(9)。

11 + 15 = 9?NO11 + 2 = 9?NO11 + 7 = 9?NO

由于这些数字对都不等于目标(9),那么我们就移到列表中的第二个数字(15)并检查所有可能的数字对。由于我们之前已经检查过 11+15,所以不需要再检查一次。

15 + 2 = 9?NO15 + 7 = 9?NO

由于这些数字对都不等于目标(9),那么我们就移到列表中的第三个数字(2)并检查所有可能的数字对。

2 + 7 = 9?YES!!!

现在,我们已经找到了和为目标的数字对,我们应该返回 [2,3],因为这表示数字对在数组中的索引位置。

蛮力法 JavaScript 解决方案和时间复杂度

这个解决方案使用了嵌套的 for 循环,时间复杂度为 O(n²)。外部循环用于获取列表中的当前数字,内部循环用于检查当前数字和列表中的其他数字的和是否等于目标。

/** * @param {number[]} nums * @param {number} target * @return {number[]} */var twoSum = function (nums, target) {  if (nums.length === 2) return [0, 1];  for (let i = 0; i < nums.length; i++) {    const currentNum = nums[i];    for (let j = i + 1; j < nums.length; j++) {      if (currentNum + nums[j] === target) return [i, j];    }  }};

注意:在我的解决方案中,我添加了一个额外的检查来检查输入数组是否只有两个数字。在这种情况下,我们立即返回[0,1],因为这是该测试用例的唯一可能的索引。

 if (nums.length === 2) return [0, 1];

到目前为止,我们的输入数组都非常小。但是如果我们有一个包含100个、500个或1000个以上数字的输入数组,那么找到总和为目标的一对数字可能需要一些时间。

在下一节中,我们将介绍一种利用JavaScript的Map对象并以线性时间O(n)运行的解决方法。

Map的方法和解决方案

在暴力解决方案中,我们从数组的开头开始比较所有可能的数字对,直到找到总和为目标的那对数字。但在这个方法中,我们可以使用JavaScript的Map对象和一个for循环来找到那对数字。

JavaScript的Map对象是一个键-值对的集合,可以快速查找,并且具有内置的set()get()has()等方法。

让我们继续使用之前的例子:

[11, 15, 2, 7]目标:9

我们开始循环遍历数组,并查看列表中的当前数字,即nums[i]。我们还要创建一个新的map对象,一开始是空的。

const map = new Map();for(let i=0; i<nums.length; i++){    }

在我们的循环体内,我们需要计算差值,即目标减去当前数字。

    const map = new Map();    for(let i=0; i<nums.length; i++){        const difference = target - nums[i]    }

因为我们知道只有两个数字可以相加得到目标,所以我们可以检查差值是否在map中。如果是,则我们找到了一对数字,可以返回索引。否则,我们将当前数字和它的索引添加到map中。

    const map = new Map();    for(let i=0; i < nums.length; i++) {        const difference = target - nums[i]        if(map.has(difference)) {            return [map.get(difference), i]        } else {            map.set(nums[i], i)        }    }

在以下代码中,has()方法用于检查map对象中是否存在指定的key。该方法返回一个布尔值(true或false)。

map.has(difference)

get()方法用于返回map中的指定元素。

 if(map.has(difference)) {   return [map.get(difference), i] } 

set()方法用于在map中添加或更新一个键和值。

else {  map.set(nums[i], i)}

以下是这种方法的完整代码:

/** * @param {number[]} nums * @param {number} target * @return {number[]} */var twoSum = function(nums, target) {    if(nums.length === 2) return [0,1]    const map = new Map();    for (let i = 0; i < nums.length; i++) {        const difference = target - nums[i]        if (map.has(difference)) {            return [map.get(difference), i]        } else {            map.set(nums[i], i)        }    }    };

Map解决方案的时间复杂度

这个解决方案的时间复杂度是线性时间复杂度O(n)。因为我们只使用了一个循环而不是两个循环,所以我们改进了时间复杂度,不再像之前一样呈二次时间复杂度O(n²)。

在接下来的几个部分中,我们将开始构建每种方法的可视化。

Two Sum可视化概述

这个项目的目标是为map和brute force两种解决方案创建可视化。

对于brute force解决方案,我们将展示如何遍历每对数字,直到找到总和等于目标值的一对。

Screenshot-2023-11-20-at-8.43.08-AM
Brute Force解决方案可视化

对于map解决方案,我们将展示如何构建map并检查总和等于目标值的一对。

Screenshot-2023-11-20-at-8.51.40-AM
Map解决方案可视化

添加Brute Force可视化

创建constlet变量

我们首先需要创建const变量,并将其赋值为负责显示brute force解决方案按钮和输出的HTML元素的访问结果。

const bruteForceSolutionBtn = document.getElementById("brute-force-visual-btn");const bruteForceNumbersOutput = document.querySelector(  "#brute-force-output > .numbers-array");const bruteForceTextOutput = document.querySelector(  "#brute-force-output > .result-text");

接下来的步骤是创建测试用例数组和目标值的const变量,这两个变量将用于两种可视化。

const testCaseArray = [11, 15, 2, 7];const target = 9;

然后,我们需要创建let变量,表示外部循环中正在查看的当前数字和内部循环中正在查看的补数。

let currentNum;let currentCompliment;

创建getClassName函数

在我们的可视化中,我们希望通过在当前数字周围应用不同颜色的边框来向用户显示我们正在检查的当前数字对。当前数字将有一个绿色边框,当前补数将有一个蓝色边框。

Screenshot-2023-11-20-at-10.02.46-AM

为了实时更新这些样式,我们需要创建一个负责向当前数字和补数添加适当类名的函数。

首先,创建一个新的getClassName函数,它接受一个num参数。

const getClassName = (num) => {    };

在该函数内,创建一个switch语句,使用num作为表达式。

const getClassName = (num) => {  switch (num) {      }};

第一个case应检查currentNum并返回current-num类的字符串。

const getClassName = (num) => {  switch (num) {    case currentNum:      return "class='current-num'";  }};

第二个case应检查currentCompliment并返回compliment-num类的字符串。

const getClassName = (num) => {  switch (num) {    case currentNum:      return "class='current-num'";    case currentCompliment:      return "class='compliment-num'";  }};

对于default情况,应该返回一个空字符串,因为我们不打算给该元素应用任何类。

const getClassName = (num) => {  switch (num) {    case currentNum:      return "class='current-num'";    case currentCompliment:      return "class='compliment-num'";    default:      return "";  }};

创建bruteForceApproach函数

此函数将负责执行蛮力解法并在页面上显示可视化效果。

我们首先需要创建bruteForceApproach函数,这将是一个async函数。

const bruteForceApproach = async () => {};

然后,我们需要在测试用例数组中添加外部循环。

const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { }};

for循环内部,将currentNum更新为当前正在检查的测试用例数组中的数字。

const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; }};

接下来,创建内部for循环。

const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j < testCaseArray.length; ++j) { } }};

在内部for循环中,更新currentCompliment数字,并将其赋值为testCaseArray[j]的值。这表示每个数字都在当前数字右侧。

const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j < testCaseArray.length; ++j) { currentCompliment = testCaseArray[j]; } }};

接下来,我们需要添加一个setTimeout,它将延迟一秒钟来更改标记中的可视化效果。这将帮助创建显示不同数字对的动画效果。

const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j < testCaseArray.length; ++j) { currentCompliment = testCaseArray[j]; await new Promise((resolve) => setTimeout(resolve, 1000)); } }};

然后,我们需要更新输出的HTML。首先将测试用例数组赋值给bruteForceNumbersOutput.innerHTML

const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j < testCaseArray.length; ++j) { currentCompliment = testCaseArray[j]; await new Promise((resolve) => setTimeout(resolve, 1000)); bruteForceNumbersOutput.innerHTML = testCaseArray; } }};

接下来,我们要使用数组的map方法,创建一个新的span元素数组,表示数组中的每个数字以及样式。我们还需要在此方法上链接join方法,以删除map方法在创建新数组时添加的逗号。

const bruteForceApproach = async () => {  for (let i = 0; i < testCaseArray.length; ++i) {    currentNum = testCaseArray[i];    for (let j = i + 1; j < testCaseArray.length; ++j) {      currentCompliment = testCaseArray[j];      await new Promise((resolve) => setTimeout(resolve, 1000));      bruteForceNumbersOutput.innerHTML = testCaseArray        .map(          (num, index) =>            `            <span ${getClassName(num)}>            ${testCaseArray[index]}            </span>          `        )        .join("");    }  }};

如果我们找不到两个数相加等于目标值,那么我们想向用户显示一条消息。更新bruteForceTextOutput的文本内容,并将其赋值为以下消息:

const bruteForceApproach = async () => {  for (let i = 0; i < testCaseArray.length; ++i) {    currentNum = testCaseArray[i];    for (let j = i + 1; j < testCaseArray.length; ++j) {      currentCompliment = testCaseArray[j];      await new Promise((resolve) => setTimeout(resolve, 1000));      bruteForceNumbersOutput.innerHTML = testCaseArray        .map(          (num, index) =>            `            <span ${getClassName(num)}>            ${testCaseArray[index]}            </span>          `        )        .join("");      bruteForceTextOutput.textContent = `Does the sum of ${currentNum} + ${currentCompliment} equal ${target}? NO!`;    }  }};

最后一部分是添加一个条件来检查是否找到了两个数相加等于目标值。如果是,那么我们可以显示最终的索引数组,并从函数中return

  if (currentNum + currentCompliment === target) {      bruteForceTextOutput.textContent = `Final indices: [${i}, ${j}]`;      return;  }

为了测试暴力解法的可视化效果,我们需要为bruteForceSolutionBtn添加一个事件监听器。事件监听器应该监听"click"事件,并引用bruteForceApproach函数。

bruteForceSolutionBtn.addEventListener("click", bruteForceApproach);

这应该是你迄今为止的完整代码:

const bruteForceSolutionBtn = document.getElementById("brute-force-visual-btn");const bruteForceNumbersOutput = document.querySelector(  "#brute-force-output > .numbers-array");const bruteForceTextOutput = document.querySelector(  "#brute-force-output > .result-text");const testCaseArray = [11, 15, 2, 7];const target = 9;let currentNum;let currentCompliment;const getClassName = (num) => {  switch (num) {    case currentNum:      return "class='current-num'";    case currentCompliment:      return "class='compliment-num'";    default:      return "";  }};const bruteForceApproach = async () => {  for (let i = 0; i < testCaseArray.length; ++i) {    currentNum = testCaseArray[i];    for (let j = i + 1; j < testCaseArray.length; ++j) {      currentCompliment = testCaseArray[j];      await new Promise((resolve) => setTimeout(resolve, 1000));      bruteForceNumbersOutput.innerHTML = testCaseArray        .map(          (num, index) =>            `            <span ${getClassName(num)}>            ${testCaseArray[index]}            </span>          `        )        .join("");      bruteForceTextOutput.textContent = `Does the sum of ${currentNum} + ${currentCompliment} equal ${target}? NO!`;      if (currentNum + currentCompliment === target) {        bruteForceTextOutput.textContent = `Final indices: [${i}, ${j}]`;        return;      }    }  }};bruteForceSolutionBtn.addEventListener("click", bruteForceApproach);

通过点击“Show Visualization”按钮来测试你的暴力解法可视化效果。

Screenshot-2023-11-20-at-10.42.51-AM
点击“show visualization”按钮后的结果

如何在动画进行时禁用bruteForceSolutionBtn

如果你连续多次点击bruteForceSolutionBtn,你会看到动画出现故障。为了修复这个问题,我们应该在动画运行时禁用按钮,然后在动画完成时重新启用它。

bruteForceApproach函数内,为bruteForceSolutionBtn设置disabled属性。

const bruteForceApproach = async () => {  bruteForceSolutionBtn.setAttribute("disabled", "");

if语句内,移除bruteForceSolutionBtn的disabled属性。

   if (currentNum + currentCompliment === target) {        bruteForceTextOutput.textContent = `最终索引:[${i}, ${j}]`;        bruteForceSolutionBtn.removeAttribute("disabled");        return;   }

以下是修复后的完整代码:

const bruteForceSolutionBtn = document.getElementById("brute-force-visual-btn");const bruteForceNumbersOutput = document.querySelector(  "#brute-force-output > .numbers-array");const bruteForceTextOutput = document.querySelector(  "#brute-force-output > .result-text");const testCaseArray = [11, 15, 2, 7];const target = 9;let currentNum;let currentCompliment;const getClassName = (num) => {  switch (num) {    case currentNum:      return "class='current-num'";    case currentCompliment:      return "class='compliment-num'";    default:      return "";  }};const bruteForceApproach = async () => {  bruteForceSolutionBtn.setAttribute("disabled", "");  for (let i = 0; i < testCaseArray.length; ++i) {    currentNum = testCaseArray[i];    for (let j = i + 1; j  setTimeout(resolve, 1000));      bruteForceNumbersOutput.innerHTML = testCaseArray        .map(          (num, index) =>            `                        ${testCaseArray[index]}                      `        )        .join("");      bruteForceTextOutput.textContent = `是否等于目标和${target}:NO!`;      if (currentNum + currentCompliment === target) {        bruteForceTextOutput.textContent = `最终索引:[${i}, ${j}]`;        bruteForceSolutionBtn.removeAttribute("disabled");        return;      }    }  }};bruteForceSolutionBtn.addEventListener("click", bruteForceApproach);

再次尝试可视化,现在你应该看到动画运行时按钮被禁用了。

Screenshot-2023-11-20-at-10.56.36-AM
展示了动画运行时按钮被禁用的图片

添加Map解决方案可视化

创建const变量

我们要创建一些代表最优解按钮元素、可视化输出和表格元素的新的const变量。

const optimalSolutionBtn = document.getElementById("optimal-visual-btn");const currentValueOutput = document.getElementById("current-value-output");const finalOptimalSolutionResult = document.getElementById(  "final-optimal-result");const table = document.getElementById("table-output");const tableBodyOutput = document.getElementById("map-table-body");

以下是两个可视化的变量声明的完整列表:

const bruteForceSolutionBtn = document.getElementById("brute-force-visual-btn");const bruteForceNumbersOutput = document.querySelector(  "#brute-force-output > .numbers-array");const bruteForceTextOutput = document.querySelector(  "#brute-force-output > .result-text");const optimalSolutionBtn = document.getElementById("optimal-visual-btn");const currentValueOutput = document.getElementById("current-value-output");const finalOptimalSolutionResult = document.getElementById(  "final-optimal-result");const table = document.getElementById("table-output");const tableBodyOutput = document.getElementById("map-table-body");const testCaseArray = [11, 15, 2, 7];const target = 9;let currentNum;let currentCompliment;

创建optimalApproach异步函数

首先我们需要创建一个新的optimalApproach函数,这将是一个异步函数。你可以将它添加在bruteForceApproach函数的下面。

const optimalApproach = async () => {    };

bruteForceApproach函数一样,我们希望在动画开始时禁用按钮,防止用户多次点击破坏动画。

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");};

当页面加载时,默认情况下隐藏表格。我们希望在动画开始时显示表格元素。

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";};

每次运行这个动画时,我们希望向用户显示是否已经找到了正确的配对。在动画开始时,我们希望清除之前的输出。

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";};

接下来,我们需要创建一个空的map对象,这个对象将在for循环中随着时间的推移而更新。

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();};

接下来,我们需要创建一个for循环,负责循环遍历数组中的每个数字,并随着时间的推移更新动画。

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {      }};

在循环内部,我们需要添加计算差值的表达式。

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];  }};

然后,我们需要添加一个延迟2秒的setTimeout,用于在HTML标记中延迟更改,以达到动画效果。

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];    await new Promise((resolve) => setTimeout(resolve, 2000));  }};

然后,我们需要添加一个if语句来检查map中是否存在差值。

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];    await new Promise((resolve) => setTimeout(resolve, 2000));    if (map.has(difference)) {            }  }};

if语句内部,我们需要更新文本的内容来显示屏幕上最终的索引数组结果。我们将使用mapget方法来获取索引值。

 if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `Final indices: [${map.get(        difference      )}, ${i}]`; }

我们还需要更新输出,以显示找到了加起来等于目标值的数字对。

  if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `最终索引: [${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>差异(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>      <p>差异(${difference})是否在我们的Map中? 是的, 我们找到了一对相加等于目标的数字.</p>    `;  }

我们还需要从函数中移除optimalSolutionBtn的disabled属性,并从该函数中返回。

  if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `最终索引: [${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>差异(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>      <p>差异(${difference})是否在我们的Map中? 是的, 我们找到了一对相加等于目标的数字.</p>    `;      optimalSolutionBtn.removeAttribute("disabled");      return;  }

如果找不到一对数字,则我们需要添加一个else子句。

  if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `最终索引: [${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>差异(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>      <p>差异(${difference})是否在我们的Map中? 是的, 我们找到了一对相加等于目标的数字.</p>    `;      optimalSolutionBtn.removeAttribute("disabled");      return;  } else {          }

else子句中,我们需要更新消息以表示我们尚未找到一对数字,并且正在添加testCaseArray[i]以及其索引值到map中。

else {      currentValueOutput.innerHTML = `        <p>差异(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>        <p>差异(${difference})是否在我们的Map中? 不.</p>        <p>将当前数字 ${testCaseArray[i]} 添加到我们的Map中.</p>      `;}

然后,我们需要更新表格输出,包括当前数字和其索引值。

else {      currentValueOutput.innerHTML = `        <p>差异(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>        <p>差异(${difference})是否在我们的Map中? 不.</p>        <p>将当前数字 ${testCaseArray[i]} 添加到我们的Map中.</p>      `;      tableBodyOutput.innerHTML += `      <tr>        <td>${testCaseArray[i]}</td>        <td>${i}</td>      </tr>    `;}

最后,使用set方法将当前数字和索引值设置到map中。

else {      currentValueOutput.innerHTML = `        <p>差异(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>        <p>差异(${difference})是否在我们的Map中? 不.</p>        <p>将当前数字 ${testCaseArray[i]} 添加到我们的Map中.</p>      `;      tableBodyOutput.innerHTML += `      <tr>        <td>${testCaseArray[i]}</td>        <td>${i}</td>      </tr>    `;      map.set(testCaseArray[i], i);}

以下是optimalApproach函数的完整代码。

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];    await new Promise((resolve) => setTimeout(resolve, 2000));    if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `最终索引: [${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>差异(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>      <p>差异(${difference})是否在我们的Map中? 是的, 我们找到了一对相加等于目标的数字.</p>    `;      optimalSolutionBtn.removeAttribute("disabled");      return;    } else {      currentValueOutput.innerHTML = `        <p>差异(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>        <p>差异(${difference})是否在我们的Map中? 不.</p>        <p>将当前数字 ${testCaseArray[i]} 添加到我们的Map中.</p>      `;      tableBodyOutput.innerHTML += `      <tr>        <td>${testCaseArray[i]}</td>        <td>${i}</td>      </tr>    `;      map.set(testCaseArray[i], i);    }  }};

为了测试这个可视化效果,请为optimalSolutionBtn添加事件监听器,并传入"click"事件和optimalApproach函数引用。

optimalSolutionBtn.addEventListener("click", optimalApproach);

当你点击“显示可视化”按钮来查看地图解决方案时,你应该会看到下面的动画:

Screenshot-2023-11-20-at-12.05.05-PM
地图解决方案的可视化效果

如何重置地图解决方案的表格输出

如果你尝试多次运行动画,你会发现表格显示的是上一次运行的结果。

Screenshot-2023-11-20-at-12.09.35-PM
显示上一次运行结果的表格

为了解决这个问题,我们可以在每次运行动画之前重置表格。首先,在optimalApproach函数之前创建一个resetTable函数。

const resetTable = () => {    };const optimalApproach = async () => {    .........

在该函数内部清空表格和最终文本结果。

const resetTable = () => {  tableBodyOutput.innerHTML = "";  finalOptimalSolutionResult.textContent = "";};

optimalApproach函数内调用resetTable函数以查看重置表格的结果。

const optimalApproach = async () => {  resetTable();  optimalSolutionBtn.setAttribute("disabled", "");    ...........

再次测试你的动画,现在你应该会看到每次新的动画运行之前都会重置表格结果。

最终解决方案代码和实时示例

下面是两种可视化方案的完整JavaScript代码:

const bruteForceSolutionBtn = document.getElementById("brute-force-visual-btn");const bruteForceNumbersOutput = document.querySelector(  "#brute-force-output > .numbers-array");const bruteForceTextOutput = document.querySelector(  "#brute-force-output > .result-text");const optimalSolutionBtn = document.getElementById("optimal-visual-btn");const currentValueOutput = document.getElementById("current-value-output");const finalOptimalSolutionResult = document.getElementById(  "final-optimal-result");const table = document.getElementById("table-output");const tableBodyOutput = document.getElementById("map-table-body");const testCaseArray = [11, 15, 2, 7];const target = 9;let currentNum;let currentCompliment;const getClassName = (num) => {  switch (num) {    case currentNum:      return "class='current-num'";    case currentCompliment:      return "class='compliment-num'";    default:      return "";  }};const bruteForceApproach = async () => {  bruteForceSolutionBtn.setAttribute("disabled", "");  for (let i = 0; i < testCaseArray.length; ++i) {    currentNum = testCaseArray[i];    for (let j = i + 1; j < testCaseArray.length; ++j) {      currentCompliment = testCaseArray[j];      await new Promise((resolve) => setTimeout(resolve, 1000));      bruteForceNumbersOutput.innerHTML = testCaseArray        .map(          (num, index) =>            `              <span ${getClassName(num)}>              ${testCaseArray[index]}              </span>            `        )        .join("");      bruteForceTextOutput.textContent = `是否将${currentNum} + ${currentCompliment}之和等于${target}?不等!`;      if (currentNum + currentCompliment === target) {        bruteForceTextOutput.textContent = `最终索引:[${i}, ${j}]`;        bruteForceSolutionBtn.removeAttribute("disabled");        return;      }    }  }};const resetTable = () => {  tableBodyOutput.innerHTML = "";  finalOptimalSolutionResult.textContent = "";};const optimalApproach = async () => {  resetTable();  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];    await new Promise((resolve) => setTimeout(resolve, 2000));    if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `最终索引:[${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>差(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>      <p>差(${difference})是否存在于我们的映射中?是的,我们找到了那一对加起来等于目标值的数字。</p>    `;      optimalSolutionBtn.removeAttribute("disabled");      return;    } else {      currentValueOutput.innerHTML = `        <p>差(${difference}) = 目标(${target}) - 当前数字(${testCaseArray[i]})</p>        <p>差(${difference})是否存在于我们的映射中?否。</p>        <p>将当前数字 ${testCaseArray[i]} 添加到我们的映射中。</p>      `;      tableBodyOutput.innerHTML += `        <tr>          <td>${testCaseArray[i]}</td>          <td>${i}</td>        </tr>      `;      map.set(testCaseArray[i], i);    }  }};bruteForceSolutionBtn.addEventListener("click", bruteForceApproach);optimalSolutionBtn.addEventListener("click", optimalApproach);

这里是一个链接,再次指向CodePen上的最终结果。

结论

希望您喜欢这个项目,并对Two Sum问题的工作原理有所了解。

我鼓励您自己尝试一下这个项目,并可能添加一些新功能,比如尝试不同的数字或要求用户输入数字数组和目标值。👍🏾


Leave a Reply

Your email address will not be published. Required fields are marked *