一、前置知识(可跳过)
1. nums.sort()
sort()
是 JavaScript 数组对象的一个内置方法,用于对数组的元素进行排序。- 默认情况下,
sort()
方法会将数组的元素转换为字符串,然后按照 Unicode 码点的顺序进行排序。 这意味着,如果数组包含数字,默认排序可能会产生意想不到的结果(例如,[1, 10, 2]
会被排序为[1, 10, 2]
,因为字符串 "1" 小于字符串 "10",也小于字符串 "2")。
2. (a, b) => a - b
(比较函数)
- 为了对数字进行正确排序(升序排列),我们需要提供一个比较函数 给
sort()
方法。 比较函数定义了两个元素a
和b
应该如何比较。 (a, b) => a - b
是一个箭头函数,它接受两个参数a
和b
(代表数组中的两个元素),并返回a - b
的结果。
3. 比较函数的返回值规则
比较函数的返回值决定了 a
和 b
的相对顺序:
- 如果
a - b
小于 0 (负数): 说明a
应该排在b
前面,也就是a
比b
小。 - 如果
a - b
等于 0: 说明a
和b
相等,它们的相对顺序不变。 - 如果
a - b
大于 0 (正数): 说明a
应该排在b
后面,也就是a
比b
大。
4. 完整过程
将以上两部分结合起来,nums.sort((a, b) => a - b)
的作用是:
- 对
nums
数组调用sort()
方法。 sort()
方法会遍历nums
数组,每次取出两个元素a
和b
,并将它们传递给比较函数(a, b) => a - b
。- 比较函数计算
a - b
的值,并根据返回值,sort()
方法决定a
和b
的相对位置,从而实现升序排序。
5. 示例
css
const nums = [3, 1, 4, 1, 5, 9, 2, 6];
nums.sort((a, b) => a - b);
console.log(nums); // 输出: [1, 1, 2, 3, 4, 5, 6, 9]
在这个例子中,nums
数组经过 sort((a, b) => a - b)
排序后,会按照从小到大的顺序排列。
6. 为什么要使用 a - b
?
a - b
是一种简洁且直接的数字比较方式。- 如果你需要降序排列,可以使用
(a, b) => b - a
。 - 对于字符串的比较,可以使用
a.localeCompare(b)
来进行基于本地语言环境的排序。
总结
nums.sort((a, b) => a - b)
这段代码实现了对 nums
数组进行数值升序排序 。 比较函数的 a - b
是实现数值排序的关键,它保证 sort()
方法能够正确地比较数字的大小并调整数组元素的顺序。
二、题目描述
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例 1:
scss
输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
ini
输入: nums = [0,1,1]
输出: []
解释: 唯一可能的三元组和不为 0 。
示例 3:
lua
输入: nums = [0,0,0]
输出: [[0,0,0]]
解释: 唯一可能的三元组和为 0 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105
三、题解(双指针)
js
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
nums.sort((a, b) => a - b);
const result = [];
const n = nums.length;
for (let i = 0; i < n - 2; i++) {
if (nums[i] > 0) break;
if (i > 0 && nums[i] === nums[i - 1]) continue;
let left = i + 1;
let right = n - 1;
while (left < right) {
const sum = nums[i] + nums[left] + nums[right];
if (sum === 0) {
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 < 0) {
left++;
} else {
right--;
}
}
}
return result;
};
核心思想
核心思想:排序 + 双指针 + 细节优化
-
排序 (Sorting): 通过排序将问题转化为在有序数组中寻找目标值,为双指针技巧创造条件。
-
双指针 (Two Pointers): 利用双指针高效地在有序数组中寻找两个数,使它们与当前固定数之和等于目标值(本题中目标值为0)。
-
细节优化 (Fine-grained Optimizations):
- 提前终止 (Early Termination): 如果当前固定数已经大于0,则直接结束循环,因为不可能找到和为0的三元组。
- 跳过重复元素 (Skip Duplicates): 在固定第一个数和移动双指针时,都跳过重复的数值,避免产生重复的结果。
详细解析
-
/** @param {number[]} nums @return {number[][]} */
- 这是一段 JSDoc 注释,用于描述函数的输入参数类型和返回值类型。
@param {number[]} nums
表示函数接受一个名为nums
的参数,它是一个数字数组。@return {number[][]}
表示函数返回一个数字数组的数组(一个二维数组),其中每个内部数组都是一个三元组。
-
var threeSum = function(nums) {
- 定义一个名为
threeSum
的函数,它接受一个参数nums
(一个数字数组)。
- 定义一个名为
-
nums.sort((a, b) => a - b);
-
对输入的数组
nums
进行升序排序。sort()
方法默认按字符串顺序排序,所以我们需要传入一个比较函数(a, b) => a - b
来实现数字的升序排序。 -
重要性: 排序是解决这个问题的关键。
- 它使得我们可以使用双指针技巧。
- 它使得我们可以更容易地跳过重复的元素,避免生成重复的结果。
-
-
const result = [];
- 创建一个空数组
result
,用于存储所有找到的三元组。
- 创建一个空数组
-
const n = nums.length;
- 获取数组
nums
的长度,并将其存储在变量n
中。 这样做是为了避免在循环中重复计算数组长度,提高代码效率。
- 获取数组
-
for (let i = 0; i < n - 2; i++) {
- 外层循环遍历数组
nums
,循环变量i
从 0 开始,到n - 2
结束。 i < n - 2
的原因:我们需要至少三个元素才能组成一个三元组,所以留出至少两个元素给left
和right
指针。nums[i]
将作为三元组的第一个元素。
- 外层循环遍历数组
-
if (nums[i] > 0) break;
(优化 1: 提前终止)- 优化目的: 如果当前元素
nums[i]
大于 0,由于数组已经排序,后面的所有元素也都会大于 0。 因此,不可能找到三个数之和等于 0 的三元组。 - 作用: 提前结束循环,避免不必要的计算,提高代码效率。
- 优化目的: 如果当前元素
-
if (i > 0 && nums[i] === nums[i - 1]) continue;
(优化 2: 跳过重复元素)-
优化目的: 避免生成重复的三元组。
-
解释:
i > 0
: 确保i - 1
是一个有效的索引,防止数组越界。nums[i] === nums[i - 1]
: 检查当前元素nums[i]
是否与前一个元素nums[i - 1]
相同。- 如果两个条件都满足,则说明我们已经考虑过以
nums[i - 1]
作为第一个元素的所有可能的三元组。 continue
: 跳过当前循环迭代,继续下一个元素。
-
-
let left = i + 1;
left
指针初始化为i + 1
。left
指针指向nums[i]
之后的第一个元素,它将作为三元组的第二个元素。
-
let right = n - 1;
right
指针初始化为n - 1
,指向数组的最后一个元素。right
指针将作为三元组的第三个元素。
-
while (left < right) {
while
循环,只要left
指针小于right
指针,就继续执行。 这确保我们总是考虑两个不同的元素,并且left
指针始终在right
指针的左侧。
-
const sum = nums[i] + nums[left] + nums[right];
- 计算当前三个元素的和
sum
。nums[i]
是固定的,nums[left]
和nums[right]
是在while
循环中调整的。
- 计算当前三个元素的和
-
if (sum === 0) {
- 如果
sum
等于 0,表示我们找到了一个满足条件的三元组。
- 如果
-
result.push([nums[i], nums[left], nums[right]]);
- 将找到的三元组 [nums[i], nums[left], nums[right]] 添加到
result
数组中。
- 将找到的三元组 [nums[i], nums[left], nums[right]] 添加到
-
while (left < right && nums[left] === nums[left + 1]) left++;
(优化 3: 跳过重复元素 - left)-
优化目的: 避免生成重复的三元组。
-
解释:
left < right
: 确保left
指针仍然在right
指针的左侧,防止数组越界。nums[left] === nums[left + 1]
: 检查left
指针指向的元素是否与它后面的元素相同。- 如果以上两个条件都满足,说明
nums[left]
是一个重复的元素,我们需要跳过它。 left++
: 将left
指针向右移动一位,继续检查下一个元素。
-
-
while (left < right && nums[right] === nums[right - 1]) right--;
(优化 3: 跳过重复元素 - right)-
优化目的: 避免生成重复的三元组。
-
解释: 与跳过重复的
left
指针的逻辑类似,只是方向相反。- 如果以上两个条件都满足,说明
nums[right]
是一个重复的元素,我们需要跳过它。 right--
: 将right
指针向左移动一位,继续检查下一个元素。
- 如果以上两个条件都满足,说明
-
-
left++;
- 找到一个有效的三元组后,将
left
指针向右移动一位,继续寻找新的三元组。 即使sum
等于 0,我们也需要移动指针,因为可能会有其他的三元组以nums[i]
开头。
- 找到一个有效的三元组后,将
-
right--;
- 找到一个有效的三元组后,将
right
指针向左移动一位,继续寻找新的三元组。
- 找到一个有效的三元组后,将
-
else if (sum < 0) {
- 如果
sum
小于 0,说明当前三个元素的和太小,需要增加和的值。
- 如果
-
left++;
- 将
left
指针向右移动一位。 由于数组是排序的,向右移动left
指针会选择一个更大的元素,从而增加sum
的值。
- 将
-
else {
- 如果
sum
大于 0,说明当前三个元素的和太大,需要减小和的值。
- 如果
-
right--;
- 将
right
指针向左移动一位。 由于数组是排序的,向左移动right
指针会选择一个更小的元素,从而减小sum
的值。
- 将
-
return result;
for
循环结束后,返回包含所有找到的三元组的result
数组。
实例与展示(在编译器上运行下面代码得到动态示意图)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>三数之和算法交互式图示(长示例)</title>
<style>
body {
font-family: 'Arial', sans-serif;
max-width: 1000px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
color: #333;
}
h1, h2 {
color: #2c3e50;
}
.array-container {
display: flex;
justify-content: center;
margin: 30px 0;
position: relative;
height: 120px;
flex-wrap: wrap;
}
.array-element {
width: 50px;
height: 50px;
border: 2px solid #3498db;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 5px;
font-weight: bold;
background-color: #f8f9fa;
transition: all 0.3s;
position: relative;
flex-shrink: 0;
}
.array-element.active {
background-color: #3498db;
color: white;
transform: scale(1.1);
}
.array-element.fixed {
background-color: #e74c3c;
color: white;
}
.array-element.left-pointer {
background-color: #2ecc71;
color: white;
}
.array-element.right-pointer {
background-color: #f39c12;
color: white;
}
.pointer-label {
position: absolute;
bottom: -25px;
font-size: 12px;
color: #7f8c8d;
white-space: nowrap;
}
.controls {
display: flex;
justify-content: center;
margin: 20px 0;
gap: 10px;
flex-wrap: wrap;
}
button {
padding: 8px 16px;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #2980b9;
}
button:disabled {
background-color: #95a5a6;
cursor: not-allowed;
}
.speed-control {
display: flex;
align-items: center;
gap: 10px;
margin-left: 20px;
}
.explanation {
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin: 20px 0;
}
.sum-display {
text-align: center;
font-size: 18px;
margin: 15px 0;
font-weight: bold;
min-height: 50px;
}
.result-container {
margin-top: 30px;
}
.result-item {
display: inline-block;
margin: 5px;
padding: 5px 10px;
background-color: #e8f4f8;
border-radius: 4px;
}
.history-steps {
margin-top: 20px;
max-height: 150px;
overflow-y: auto;
border: 1px solid #ddd;
padding: 10px;
background-color: #f9f9f9;
}
.history-step {
margin-bottom: 5px;
padding: 5px;
border-bottom: 1px solid #eee;
}
.history-step.current {
background-color: #e8f4f8;
font-weight: bold;
}
.config-panel {
background-color: #f0f7fb;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
}
.config-group {
margin-bottom: 10px;
}
label {
display: inline-block;
width: 120px;
}
select, input {
padding: 5px;
border-radius: 4px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<h1>三数之和算法交互式图示(长示例)</h1>
<div class="config-panel">
<div class="config-group">
<label for="exampleSelect">选择示例数组:</label>
<select id="exampleSelect">
<option value="example1">[-4, -2, -2, -1, 0, 1, 2, 2, 3, 4, 5, 6]</option>
<option value="example2">[-5, -3, -2, -1, 0, 0, 1, 2, 2, 3, 4, 5, 6]</option>
<option value="example3">[-6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7]</option>
<option value="custom">自定义数组</option>
</select>
</div>
<div class="config-group" id="customInputGroup" style="display:none;">
<label for="customArray">自定义数组:</label>
<input type="text" id="customArray" placeholder="例如: -2,-1,0,1,2,3">
</div>
<button id="applyConfig">应用配置</button>
</div>
<div class="controls">
<button id="prevStep">上一步</button>
<button id="nextStep">下一步</button>
<button id="autoPlay">自动播放</button>
<button id="reset">重置</button>
<div class="speed-control">
<span>播放速度:</span>
<select id="speedSelect">
<option value="500">慢速</option>
<option value="300" selected>中速</option>
<option value="100">快速</option>
</select>
</div>
</div>
<div class="sum-display" id="sumDisplay">和为: -</div>
<div class="array-container" id="arrayContainer">
<!-- 数组元素将通过JavaScript动态生成 -->
</div>
<div class="explanation" id="explanation">
<p>点击"下一步"按钮开始算法演示。算法步骤如下:</p>
<ol>
<li>首先对数组进行排序</li>
<li>固定一个数 nums[i]</li>
<li>使用双指针(left和right)在剩余部分寻找两个数,使得三数之和为0</li>
<li>跳过重复值以避免重复解</li>
</ol>
</div>
<div class="result-container">
<h2>找到的解: <span id="resultCount">0</span>个</h2>
<div id="results"></div>
</div>
<div class="history-steps" id="historySteps">
<h3>步骤历史</h3>
<div id="historyContent"></div>
</div>
<script>
// 示例数组集合
const examples = {
example1: [-4, -2, -2, -1, 0, 1, 2, 2, 3, 4, 5, 6],
example2: [-5, -3, -2, -1, 0, 0, 1, 2, 2, 3, 4, 5, 6],
example3: [-6, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7]
};
let sortedNums = [...examples.example1].sort((a, b) => a - b);
// 算法状态
let state = {
i: 0,
left: 1,
right: sortedNums.length - 1,
step: 0,
results: [],
history: [],
autoPlayInterval: null
};
// DOM元素
const arrayContainer = document.getElementById('arrayContainer');
const sumDisplay = document.getElementById('sumDisplay');
const explanation = document.getElementById('explanation');
const resultsContainer = document.getElementById('results');
const resultCount = document.getElementById('resultCount');
const historyContent = document.getElementById('historyContent');
const prevStepBtn = document.getElementById('prevStep');
const nextStepBtn = document.getElementById('nextStep');
const autoPlayBtn = document.getElementById('autoPlay');
const resetBtn = document.getElementById('reset');
const exampleSelect = document.getElementById('exampleSelect');
const customInputGroup = document.getElementById('customInputGroup');
const customArrayInput = document.getElementById('customArray');
const applyConfigBtn = document.getElementById('applyConfig');
const speedSelect = document.getElementById('speedSelect');
// 初始化数组显示
function renderArray() {
arrayContainer.innerHTML = '';
for (let j = 0; j < sortedNums.length; j++) {
const element = document.createElement('div');
element.className = 'array-element';
element.textContent = sortedNums[j];
// 标记当前指针位置
if (j === state.i) {
element.classList.add('fixed');
const label = document.createElement('div');
label.className = 'pointer-label';
label.textContent = `i=${j}`;
element.appendChild(label);
} else if (j === state.left) {
element.classList.add('left-pointer');
const label = document.createElement('div');
label.className = 'pointer-label';
label.textContent = `L=${j}`;
element.appendChild(label);
} else if (j === state.right) {
element.classList.add('right-pointer');
const label = document.createElement('div');
label.className = 'pointer-label';
label.textContent = `R=${j}`;
element.appendChild(label);
}
arrayContainer.appendChild(element);
}
// 显示当前和
if (state.step > 0 && state.left < state.right) {
const sum = sortedNums[state.i] + sortedNums[state.left] + sortedNums[state.right];
sumDisplay.textContent = `和为: ${sum} (${sortedNums[state.i]} + ${sortedNums[state.left]} + ${sortedNums[state.right]})`;
if (sum === 0) {
sumDisplay.style.color = '#27ae60';
} else if (sum < 0) {
sumDisplay.style.color = '#e74c3c';
} else {
sumDisplay.style.color = '#f39c12';
}
} else {
sumDisplay.textContent = state.i >= sortedNums.length - 2
? '算法已完成'
: '和为: -';
sumDisplay.style.color = '#333';
}
// 更新说明
updateExplanation();
// 更新历史
updateHistory();
// 更新按钮状态
updateButtonStates();
}
// 更新说明文本
function updateExplanation() {
let explanationText = '';
if (state.step === 0) {
explanationText = '<p>点击"下一步"开始算法演示。首先对数组进行排序。</p>';
} else {
const sum = state.left < state.right
? sortedNums[state.i] + sortedNums[state.left] + sortedNums[state.right]
: null;
explanationText = `
<p><strong>当前步骤 ${state.step}:</strong></p>
<p>固定元素 nums[${state.i}] = ${sortedNums[state.i]}</p>
${state.left < state.right ? `
<p>左指针 L = ${state.left} (值: ${sortedNums[state.left]})</p>
<p>右指针 R = ${state.right} (值: ${sortedNums[state.right]})</p>
<p>三数之和 = ${sum}</p>
` : '<p>准备移动固定指针 i</p>'}
`;
if (sum === 0) {
explanationText += `
<p style="color: #27ae60; font-weight: bold;">找到有效三元组!</p>
<p>将三元组添加到结果中,然后同时移动左右指针,并跳过重复值。</p>
`;
} else if (sum < 0) {
explanationText += `
<p style="color: #e74c3c;">和小于0,需要更大的数,移动左指针向右</p>
`;
} else if (sum > 0) {
explanationText += `
<p style="color: #f39c12;">和大于0,需要更小的数,移动右指针向左</p>
`;
}
if (state.i >= sortedNums.length - 2) {
explanationText += '<p style="font-weight: bold;">算法已完成所有可能的组合检查</p>';
}
}
explanation.innerHTML = explanationText;
}
// 更新步骤历史
function updateHistory() {
historyContent.innerHTML = '';
state.history.forEach((step, index) => {
const stepElement = document.createElement('div');
stepElement.className = `history-step ${index === state.history.length - 1 ? 'current' : ''}`;
let stepText = `步骤 ${index + 1}: `;
if (step.action === 'init') {
stepText += '初始化';
} else if (step.action === 'move_i') {
stepText += `移动固定指针 i 从 ${step.from} 到 ${step.to}`;
} else if (step.action === 'move_L') {
stepText += `移动左指针 L 从 ${step.from} 到 ${step.to}`;
} else if (step.action === 'move_R') {
stepText += `移动右指针 R 从 ${step.from} 到 ${step.to}`;
} else if (step.action === 'found') {
stepText += `找到解: [${step.triplet.join(', ')}]`;
} else if (step.action === 'skip_dup') {
stepText += `跳过重复值: ${step.value}`;
}
stepElement.textContent = stepText;
historyContent.appendChild(stepElement);
});
// 滚动到底部
historyContent.scrollTop = historyContent.scrollHeight;
}
// 更新按钮状态
function updateButtonStates() {
prevStepBtn.disabled = state.history.length <= 1;
nextStepBtn.disabled = state.i >= sortedNums.length - 2;
autoPlayBtn.textContent = state.autoPlayInterval ? '停止自动播放' : '自动播放';
}
// 添加历史记录
function addHistory(action, data = {}) {
state.history.push({
action,
...data,
timestamp: new Date().toLocaleTimeString()
});
}
// 下一步
function nextStep() {
if (state.i >= sortedNums.length - 2) return;
if (state.left < state.right) {
const sum = sortedNums[state.i] + sortedNums[state.left] + sortedNums[state.right];
if (sum === 0) {
// 找到解
const triplet = [sortedNums[state.i], sortedNums[state.left], sortedNums[state.right]];
state.results.push(triplet);
// 显示结果
const resultElement = document.createElement('div');
resultElement.className = 'result-item';
resultElement.textContent = `[${triplet.join(', ')}]`;
resultsContainer.appendChild(resultElement);
resultCount.textContent = state.results.length;
// 记录历史
addHistory('found', {triplet});
// 跳过重复值
while (state.left < state.right && sortedNums[state.left] === sortedNums[state.left + 1]) {
addHistory('skip_dup', {value: sortedNums[state.left], pointer: 'L'});
state.left++;
}
while (state.left < state.right && sortedNums[state.right] === sortedNums[state.right - 1]) {
addHistory('skip_dup', {value: sortedNums[state.right], pointer: 'R'});
state.right--;
}
// 移动指针
const oldLeft = state.left;
const oldRight = state.right;
state.left++;
state.right--;
addHistory('move_L', {from: oldLeft, to: state.left});
addHistory('move_R', {from: oldRight, to: state.right});
} else if (sum < 0) {
// 和太小,移动左指针
const oldLeft = state.left;
state.left++;
addHistory('move_L', {from: oldLeft, to: state.left, reason: `sum=${sum}<0`});
} else {
// 和太大,移动右指针
const oldRight = state.right;
state.right--;
addHistory('move_R', {from: oldRight, to: state.right, reason: `sum=${sum}>0`});
}
} else {
// 跳过重复的nums[i]
const oldI = state.i;
while (state.i < sortedNums.length - 2 && sortedNums[state.i] === sortedNums[state.i + 1]) {
addHistory('skip_dup', {value: sortedNums[state.i], pointer: 'i'});
state.i++;
}
state.i++;
state.left = state.i + 1;
state.right = sortedNums.length - 1;
addHistory('move_i', {from: oldI, to: state.i});
}
state.step++;
renderArray();
}
// 上一步
function prevStep() {
if (state.history.length <= 1) return;
// 移除最后一步历史
state.history.pop();
const lastState = state.history[state.history.length - 1];
// 还原状态
if (lastState) {
state.i = lastState.i || 0;
state.left = lastState.left || state.i + 1;
state.right = lastState.right || sortedNums.length - 1;
state.step = lastState.step || 0;
// 如果上一步是找到解,需要从结果中移除
if (lastState.action === 'found') {
state.results.pop();
resultsContainer.removeChild(resultsContainer.lastChild);
resultCount.textContent = state.results.length;
}
}
renderArray();
}
// 自动播放
function toggleAutoPlay() {
if (state.autoPlayInterval) {
clearInterval(state.autoPlayInterval);
state.autoPlayInterval = null;
} else {
const speed = parseInt(speedSelect.value);
state.autoPlayInterval = setInterval(() => {
if (state.i >= sortedNums.length - 2) {
clearInterval(state.autoPlayInterval);
state.autoPlayInterval = null;
updateButtonStates();
return;
}
nextStep();
}, speed);
}
updateButtonStates();
}
// 重置
function reset() {
// 停止自动播放
if (state.autoPlayInterval) {
clearInterval(state.autoPlayInterval);
state.autoPlayInterval = null;
}
// 重置状态
state = {
i: 0,
left: 1,
right: sortedNums.length - 1,
step: 0,
results: [],
history: [{action: 'init'}],
autoPlayInterval: null
};
// 清空结果
resultsContainer.innerHTML = '';
resultCount.textContent = '0';
renderArray();
}
// 应用配置
function applyConfig() {
const exampleType = exampleSelect.value;
if (exampleType === 'custom') {
try {
const customArray = customArrayInput.value
.split(',')
.map(item => parseInt(item.trim()));
if (customArray.some(isNaN)) {
throw new Error('包含非数字');
}
sortedNums = customArray.sort((a, b) => a - b);
} catch (e) {
alert('请输入有效的数字数组,例如: -2,-1,0,1,2,3');
return;
}
} else {
sortedNums = [...examples[exampleType]].sort((a, b) => a - b);
}
reset();
}
// 事件监听
nextStepBtn.addEventListener('click', nextStep);
prevStepBtn.addEventListener('click', prevStep);
autoPlayBtn.addEventListener('click', toggleAutoPlay);
resetBtn.addEventListener('click', reset);
applyConfigBtn.addEventListener('click', applyConfig);
exampleSelect.addEventListener('change', function() {
customInputGroup.style.display = this.value === 'custom' ? 'block' : 'none';
});
speedSelect.addEventListener('change', function() {
if (state.autoPlayInterval) {
clearInterval(state.autoPlayInterval);
toggleAutoPlay();
}
});
// 初始化
reset();
</script>
</body>
</html>
四、结语
再见!