一分钟解决“3.无重复字符的最长字串问题”(最优解)

废话不多说,直接上题目

题目------无重复字符的最长字串问题

给定一个字符串 s ,请你找出其中不含有重复字符的 最长 子串的长度。

示例 1:

ini 复制代码
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

ini 复制代码
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

makefile 复制代码
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列, 不是子串。

提示:

  • 0 <= s.length <= 5 * 104
  • s 由英文字母、数字、符号和空格组成

二、我的解法

js 复制代码
var lengthOfLongestSubstring = function(s) {

    let big=0;

    for(let i=0;i<s.length;i++){

        let c=a(s,i)

        big=big>c? big:c;

    }

    return big;

};

 

function a(s,n){

    list={};

    let count=0;

    for(let i=n;i<s.length;i++){

        count++;

        if(s[i] in list){

            return count-1;

        }

        list[s[i]]=i;

    }

    return count;

}

我的解法是使用类哈希表把遍历过的字符存起来,以便于以 <math xmlns="http://www.w3.org/1998/Math/MathML"> N ( O ) N(O) </math>N(O)的时间复杂度去遍历某一趟 ,但是最终的时间复杂度还是 <math xmlns="http://www.w3.org/1998/Math/MathML"> N ( O 2 ) N(O2) </math>N(O2),不过没关系,这不是重点

三、最优解法------ 滑动窗口(Sliding Window)算法

js 复制代码
function lengthOfLongestSubstring(s) {
  // 使用Map来存储字符及其最新索引
  const charIndexMap = new Map();
  let maxLength = 0;
  let left = 0; // 滑动窗口左边界
  
  for (let right = 0; right < s.length; right++) {
      const currentChar = s[right];
      
      // 如果字符已存在且在窗口内,则移动左边界
      if (charIndexMap.has(currentChar) {
          // 取最大值是为了防止左边界回退
          left = Math.max(left, charIndexMap.get(currentChar) + 1);
      }
      
      // 更新字符的最新索引
      charIndexMap.set(currentChar, right);
      
      // 计算当前窗口大小并更新最大值
      maxLength = Math.max(maxLength, right - left + 1);
  }
  
  return maxLength;
}

题解要点

1. 初始化

  • const charIndexMap = new Map();: 创建一个 Map 对象 charIndexMap,用于存储字符及其在字符串 s 中最新出现的索引。 Map 对于快速查找字符的索引非常有用。
  • let maxLength = 0;: 初始化变量 maxLength 为 0。 这将用于存储找到的最长无重复子字符串的长度。
  • let left = 0;: 初始化滑动窗口的左边界 left 为 0。

2. 滑动窗口循环

  • for (let right = 0; right < s.length; right++) { ... }: 使用 right 变量作为滑动窗口的右边界,从字符串的第一个字符开始遍历到最后一个字符。

3. 处理当前字符

  • const currentChar = s[right];: 获取当前字符。

  • if (charIndexMap.has(currentChar)) { ... }: 检查当前字符 currentChar 是否已经存在于 charIndexMap 中。 如果存在,说明当前字符在当前窗口中已经出现过重复。

    • left = Math.max(left, charIndexMap.get(currentChar) + 1);: 如果 currentChar 已经存在于 charIndexMap 中,则需要移动左边界 left

      • charIndexMap.get(currentChar) 获取 currentChar 上一次出现的索引。
      • charIndexMap.get(currentChar) + 1 是新的左边界的候选位置,即重复字符上次出现位置的下一个位置。
      • Math.max(left, charIndexMap.get(currentChar) + 1)left 的当前值和新候选位置中的较大值。 这是至关重要的,可以防止 left 回退。 例如:字符串为 "abba",当 right 指向第二个 'b' 时,left 会更新为 2。接下来,当 right 指向 'a' 时,如果没有 Math.max,left 会错误地回退到 1。

4. 更新字符索引和最大长度

  • charIndexMap.set(currentChar, right);: 更新 currentCharcharIndexMap 中的索引为当前的 right。 无论字符是否重复,都需要更新其最新位置。
  • maxLength = Math.max(maxLength, right - left + 1);: 计算当前滑动窗口的大小(right - left + 1),并将其与当前的 maxLength 进行比较,更新 maxLength 为较大的值。

5. 返回结果

  • return maxLength;: 循环结束后,返回 maxLength,即找到的最长无重复子字符串的长度。

实例与展示

四、动态图解---(复制在编译器运行即可)

js 复制代码
<!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: 800px;
            margin: 0 auto;
            padding: 20px;
            line-height: 1.6;
        }
        .container {
            margin-bottom: 30px;
        }
        .string-display {
            font-size: 24px;
            letter-spacing: 5px;
            margin: 20px 0;
            position: relative;
            height: 60px;
        }
        .char {
            display: inline-block;
            width: 30px;
            text-align: center;
            position: relative;
        }
        .index {
            position: absolute;
            top: -20px;
            left: 50%;
            transform: translateX(-50%);
            font-size: 12px;
            color: #666;
        }
        .window {
            position: absolute;
            height: 40px;
            background-color: rgba(100, 200, 100, 0.3);
            top: 25px;
            border-radius: 5px;
            transition: all 0.5s ease;
        }
        .pointer {
            position: absolute;
            top: -15px;
            font-size: 12px;
            color: red;
        }
        .left-pointer {
            left: 0;
        }
        .right-pointer {
            right: 0;
        }
        .controls {
            margin: 20px 0;
        }
        button {
            padding: 8px 15px;
            margin-right: 10px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        button:hover {
            background-color: #45a049;
        }
        .explanation {
            background-color: #f8f8f8;
            padding: 15px;
            border-radius: 5px;
            margin-top: 20px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 20px 0;
        }
        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
    </style>
</head>
<body>
    <h1>无重复字符的最长子串 - 滑动窗口算法图解</h1>
    
    <div class="container">
        <h2>示例字符串: "abcabcbb"</h2>
        <div class="controls">
            <button id="prevBtn">上一步</button>
            <button id="nextBtn">下一步</button>
            <button id="resetBtn">重置</button>
            <span id="stepCounter">步骤: 0/8</span>
        </div>
        
        <div class="string-display" id="stringDisplay">
            <!-- 字符将通过JS动态生成 -->
        </div>
        
        <div class="explanation" id="explanation">
            <p>初始化: left = 0, right = 0, maxLen = 0</p>
            <p>当前窗口: []</p>
            <p>字符位置记录: {}</p>
        </div>
        
        <table id="stepsTable">
            <thead>
                <tr>
                    <th>步骤</th>
                    <th>right</th>
                    <th>字符</th>
                    <th>窗口</th>
                    <th>字符位置</th>
                    <th>操作</th>
                    <th>maxLen</th>
                </tr>
            </thead>
            <tbody>
                <!-- 表格内容将通过JS动态生成 -->
            </tbody>
        </table>
    </div>

    <script>
        const s = "abcabcbb";
        let currentStep = 0;
        const maxSteps = s.length;
        const charMap = {};
        let left = 0;
        let maxLen = 0;
        const stepsData = [];
        
        // 初始化字符串显示
        function initStringDisplay() {
            const stringDisplay = document.getElementById('stringDisplay');
            stringDisplay.innerHTML = '';
            
            for (let i = 0; i < s.length; i++) {
                const charElement = document.createElement('div');
                charElement.className = 'char';
                charElement.innerHTML = `
                    <span class="index">${i}</span>
                    ${s[i]}
                `;
                stringDisplay.appendChild(charElement);
            }
            
            updateWindowDisplay();
        }
        
        // 更新窗口显示
        function updateWindowDisplay() {
            // 移除旧的窗口和指针
            const oldWindow = document.querySelector('.window');
            if (oldWindow) oldWindow.remove();
            const oldPointers = document.querySelectorAll('.pointer');
            oldPointers.forEach(p => p.remove());
            
            const stringDisplay = document.getElementById('stringDisplay');
            const chars = document.querySelectorAll('.char');
            
            // 添加窗口
            if (currentStep > 0) {
                const windowElement = document.createElement('div');
                windowElement.className = 'window';
                
                const firstChar = chars[left];
                const lastChar = chars[currentStep - 1];
                
                const leftPos = firstChar.offsetLeft;
                const rightPos = lastChar.offsetLeft + lastChar.offsetWidth;
                
                windowElement.style.left = `${leftPos}px`;
                windowElement.style.width = `${rightPos - leftPos}px`;
                
                stringDisplay.appendChild(windowElement);
                
                // 添加指针
                const leftPointer = document.createElement('div');
                leftPointer.className = 'pointer left-pointer';
                leftPointer.textContent = 'left';
                leftPointer.style.left = `${leftPos}px`;
                stringDisplay.appendChild(leftPointer);
                
                const rightPointer = document.createElement('div');
                rightPointer.className = 'pointer right-pointer';
                rightPointer.textContent = 'right';
                rightPointer.style.left = `${rightPos - 15}px`;
                stringDisplay.appendChild(rightPointer);
            }
        }
        
        // 更新解释文本
        function updateExplanation() {
            const explanation = document.getElementById('explanation');
            if (currentStep === 0) {
                explanation.innerHTML = `
                    <p>初始化: left = 0, right = 0, maxLen = 0</p>
                    <p>当前窗口: []</p>
                    <p>字符位置记录: {}</p>
                `;
                return;
            }
            
            const currentChar = s[currentStep - 1];
            const windowStr = s.slice(left, currentStep);
            const charMapStr = JSON.stringify(charMap).replace(/"/g, '');
            
            let operation = `添加字符 '${currentChar}'`;
            if (charMap[currentChar] !== undefined && charMap[currentChar] >= left) {
                operation = `发现重复字符 '${currentChar}',移动 left 从 ${left} 到 ${charMap[currentChar] + 1}`;
            }
            
            explanation.innerHTML = `
                <p>步骤 ${currentStep}: right = ${currentStep - 1}, 字符 = '${currentChar}'</p>
                <p>${operation}</p>
                <p>当前窗口: [${windowStr}] (长度: ${windowStr.length})</p>
                <p>字符位置记录: ${charMapStr}</p>
                <p>最大长度更新为: ${maxLen}</p>
            `;
        }
        
        // 更新步骤表格
        function updateStepsTable() {
            const tbody = document.querySelector('#stepsTable tbody');
            tbody.innerHTML = '';
            
            for (let i = 0; i < stepsData.length; i++) {
                const step = stepsData[i];
                const row = document.createElement('tr');
                
                if (i === currentStep - 1) {
                    row.style.backgroundColor = '#ffffcc';
                }
                
                row.innerHTML = `
                    <td>${i + 1}</td>
                    <td>${step.right}</td>
                    <td>'${step.char}'</td>
                    <td>${step.window}</td>
                    <td>${step.charMap}</td>
                    <td>${step.operation}</td>
                    <td>${step.maxLen}</td>
                `;
                
                tbody.appendChild(row);
            }
        }
        
        // 执行下一步
        function nextStep() {
            if (currentStep >= maxSteps) return;
            
            const right = currentStep;
            const currentChar = s[right];
            
            // 记录步骤前的状态
            const prevLeft = left;
            const prevMaxLen = maxLen;
            
            // 更新字符位置
            if (charMap[currentChar] !== undefined && charMap[currentChar] >= left) {
                left = charMap[currentChar] + 1;
            }
            
            charMap[currentChar] = right;
            maxLen = Math.max(maxLen, right - left + 1);
            
            // 保存步骤数据
            stepsData.push({
                right: right,
                char: currentChar,
                window: s.slice(left, right + 1),
                charMap: JSON.parse(JSON.stringify(charMap)),
                operation: charMap[currentChar] !== undefined && charMap[currentChar] >= prevLeft ? 
                    `重复: 移动 left 从 ${prevLeft} 到 ${left}` : 
                    `扩展窗口`,
                maxLen: maxLen
            });
            
            currentStep++;
            updateDisplay();
        }
        
        // 执行上一步
        function prevStep() {
            if (currentStep <= 0) return;
            
            currentStep--;
            
            // 恢复状态
            if (currentStep > 0) {
                const stepData = stepsData[currentStep - 1];
                left = stepData.window.length > 0 ? 
                    s.indexOf(stepData.window[0]) : 0;
                maxLen = stepData.maxLen;
                
                // 恢复字符位置记录
                Object.keys(charMap).forEach(k => delete charMap[k]);
                Object.assign(charMap, stepData.charMap);
            } else {
                left = 0;
                maxLen = 0;
                Object.keys(charMap).forEach(k => delete charMap[k]);
            }
            
            updateDisplay();
        }
        
        // 重置
        function reset() {
            currentStep = 0;
            left = 0;
            maxLen = 0;
            Object.keys(charMap).forEach(k => delete charMap[k]);
            stepsData.length = 0;
            updateDisplay();
        }
        
        // 更新所有显示
        function updateDisplay() {
            document.getElementById('stepCounter').textContent = `步骤: ${currentStep}/${maxSteps}`;
            updateWindowDisplay();
            updateExplanation();
            updateStepsTable();
        }
        
        // 初始化
        document.addEventListener('DOMContentLoaded', () => {
            initStringDisplay();
            
            document.getElementById('nextBtn').addEventListener('click', nextStep);
            document.getElementById('prevBtn').addEventListener('click', prevStep);
            document.getElementById('resetBtn').addEventListener('click', reset);
            
            // 添加键盘控制
            document.addEventListener('keydown', (e) => {
                if (e.key === 'ArrowRight') nextStep();
                if (e.key === 'ArrowLeft') prevStep();
            });
        });
    </script>
</body>
</html>

五、结语

再见!

相关推荐
iceslime几秒前
算法设计与分析实验题-序列对齐
数据结构·c++·算法·算法设计与分析·序列对齐
羊小猪~~8 分钟前
深度学习基础--目标检测常见算法简介(R-CNN、Fast R-CNN、Faster R-CNN、Mask R-CNN、SSD、YOLO)
人工智能·深度学习·算法·yolo·目标检测·机器学习·cnn
geovindu12 分钟前
vue3: pdf.js 2.16.105 using typescript
javascript·vue.js·typescript·pdf
视频砖家17 分钟前
Web前端VSCode如何解决打开html页面中文乱码的问题(方法2)
前端·vscode·vscode乱码·vscode中文乱码·vscode中文编码
2401_8370885023 分钟前
CSS transition过渡属性
前端·css
我爱吃朱肉24 分钟前
深入理解 CSS Flex 布局:代码实例解析
前端·css
喝养乐多长不高28 分钟前
Spring Web MVC基础理论和使用
java·前端·后端·spring·mvc·springmvc
多多*33 分钟前
分布式ID设计 数据库主键自增
数据库·sql·算法·http·leetcode·oracle
D_aniel_1 小时前
排序算法-希尔排序
java·算法·排序算法·希尔排序
SuperCandyXu1 小时前
leetcode0310. 最小高度树-medium
数据结构·c++·算法·leetcode