废话不多说,直接上题目
题目------无重复字符的最长字串问题
给定一个字符串 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 * 104s由英文字母、数字、符号和空格组成
二、我的解法
            
            
              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);: 更新currentChar在charIndexMap中的索引为当前的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>
        五、结语
再见!