1

找出数组中重复的数字

n个数字,且数字都在0到n-1范围内
思路:从头到尾扫描数组每个数字,当扫描到下标为i的数字m时,首先比较m是不是等于i,如果是,继续扫描;如果不是,再拿m和第m个数字进行比较。如果他们相等,就找到第一个重复数字,如果不相等,交换两者位置。接下来重复上述过程,直到找到第一个重复数字。

function Find(arrNumbers) {
  for (var i = 0; i < arrNumbers.length; i++) {
    while(arrNumbers[i]!==i) {
      if(arrNumbers[i] === arrNumbers[arrNumbers[i]]) {
        return arrNumbers[i];
      }
      let temp = arrNumbers[i];
      arrNumbers[i] = arrNumbers[temp];
      arrNumbers[temp] = temp;
    }
  }
}
let arr = [2,3,1,0,2,5,3];
console.log(Find(arr));

代码中尽管有一个两重循环,但是每个数字最多只要交换两次就能够找到属于它自己的位置,因此总的时间复杂度是O(n)。另外,所有的操作步骤都是在输入数组上进行的,不需要额外分配内存,因此空间复杂度为O(1)。

不修改数组找出重复的数字(二分查找)

在一个长度为n+1的数组里的所有数字都在1~n的范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个重复的数字,但是不能修改输入的数组。例如,如果输入长度为8的数组{2,3,5,4,3,2,6,7},那么对应的输出是重复的数字2或者3。

思路1:
由于不能修改输入的数组,我们可以创建一个长度为n+1的辅助数组,然后逐一把原数组的每个数字复制到辅助数组。如果原数组中被复制的数字是m,则把它复制到辅助数组中下标为m的位置。如果下标为m的位置上已经有数字了,则说明该数字重复了。由于使用了辅助空间,故该方案的空间复杂度是O(n)。

思路2:
由于思路1的空间复杂度是O(n),因此我们需要想办法避免使用辅助空间。我们可以想:如果数组中有重复的数,那么n+1个0~n范围内的数中,一定有几个数的个数大于1。那么,我们可以利用这个思路解决该问题。

我们把从1~n的数字从中间的数字m分为两部分,前面一半为1~m,后面一半为m+1~n。如果1~m的数字的数目等于m,则不能直接判断这一半区间是否包含重复的数字,反之,如果大于m,那么这一半的区间一定包含重复的数字;如果小于m,另一半m+1~n的区间里一定包含重复的数字。接下来,我们可以继续把包含重复的数字的区间一分为二,直到找到一个重复的数字。

由于如果1~m的数字的数目等于m,则不能直接判断这一半区间是否包含重复的数字,我们可以逐步减少m,然后判断1~m之间是否有重复的数,即,我们可以令m=m-1,然后再计算1~m的数字的数目是否等于m,如果等于m,再令m=m-1,如果大于m,则说明1~m的区间有重复的数,如果小于m,则说明m+1~n有重复的数,不断重复此过程。

function Find(arrNumbers) {
  let start = 1;
  let end = arrNumbers.length - 1;
  while(end >= start) {
    let middle = parseInt((end - start)/2) + start;
    let count = countRange(arrNumbers,start,middle);
    if(end == start) {
      if(count > 1) {
        return start;
      }
      else {
        break;
      }
    }
    if(count > (middle - start + 1)) {
      end = middle;
    }
    else {
      start = middle + 1;
    }
  }
  return -1;
}

function countRange(arrNumbers,start,end) {
  let count = 0;
  for (var i = 0; i < arrNumbers.length; i++) {
    if(arrNumbers[i] >=start && arrNumbers[i] <= end) {
      count++;
    }
  }
  return count;
}

let arr = [2,3,5,4,3,2,6,7];
console.log(Find(arr));

上述代码按照二分查找的思路,如果输入长度为n的数组,那么函数countRange最多将被调用O(logn)次,每次需要O(n)的时间,因此总的时间复杂度是O(nlogn)。和前面提到的需要o(n)的辅助空间的算法比,这种算法相当于以时间换空间。

二维数组中的查找

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

function Find(num,arr) {
  let found = false;
  let row = 0;
  let col = arr[0].length - 1;
  while(row < arr.length && col >= 0){
    if(arr[row][col] == num) {
      found = true;
      break;
    }
    else if(arr[row][col] > num) {
      col--;
    }
    else {
      row ++;
    }
  }
  return found;
}

let arr = [
  [1,2,8,9],
  [2,4,9,12],
  [4,7,10,13],
  [6,8,11,15]
];

console.log(Find(7,arr));

替换空格

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy。则经过替换之后的字符串为We%20Are%20Happy。

1、直接用空格将字符串切割成数组,再用20%进行连接。

function replaceSpace(str)
{
    return str.split(' ').join('%20');
}

2.用正则表达式找到所有空格依次替换

function replaceSpace(str)
{
    return str.replace(/\s/g,'%20');
}

3.因为javascript提供了非常便利的api方便我们解决这个问题,但是,如果是用C++来解决呢?

思路:前提是我们要在原数组上面操作。假设这个数组后面还有足够的空间。如果从前往后遍历数组,遇到一个空格就把剩下的元素全部后移2位,这样的时间复杂度是O(n^2),显然不可取。再换一个思路,先遍历一次字符串,统计出字符串中空格的总数,并可以由此计算出替换之后的字符串的总长度。每替换一个空格,长度增加2,因此替换以后字符串的长度等于原来的长度加上2乘以空格数目。然后我我们从后向前替换。时间复杂度为O(n)。

class Solution {
public:
    void replaceSpace(char *str,int length) {
        int i,j,sum=0,tmp_len;
        for(i=0;i<length;i++)
        {
            if(str[i]==' ')
            {
                sum++;
            }
        }
        tmp_len = length+2*sum-1;
        j=length-1;
        while(j>=0)
        {
            if(str[j]==' ')
            {
                j--;
                str[tmp_len--]='0';
                str[tmp_len--]='2';
                str[tmp_len--]='%';
            }
            else
            {
                str[tmp_len--]=str[j--];
            }
        }
        
    }
};

表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。 例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

1、解法一:正则表达式

/^[+-]?[0-9]*(\.[0-9]*)?([eE][+-]?[0-9]+)?$/.test("12e+4.3"); // false

附上正则表达式手册:
http://tool.oschina.net/uploa...

2、解法二:考虑各种情况,遍历字符串每个字符来判断。

考虑完全所有情况
1.只能出现数字、符号位、小数点、指数位
2.小数点,指数符号只能出现一次、且不能出现在开头结尾
3.指数位出现后,小数点不允许在出现
4.符号位只能出现在开头和指数位后面

function isNumeric(s) {
  if (s == undefined) {
    return false;
  }
  let hasPoint = false;
  let hasExp = false;
  for (let i = 0; i < s.length; i++) {
    const target = s[i];
    if (target >= 0 && target <= 9) {
      continue;
    } else if (target === 'e' || target === 'E') {
      if (hasExp || i === 0 || i === s.length - 1) {
        return false;
      } else {
        hasExp = true;
        continue;
      }
    } else if (target === '.') {
      if (hasPoint || hasExp || i === 0 || i === s.length - 1) {
        return false;
      } else {
        hasPoint = true;
        continue;
      }
    } else if (target === '-' || target === '+') {
      if (i === 0 || s[i - 1] === 'e' || s[i - 1] === 'E') {
        continue;
      } else {
        return false;
      }
    } else {
      return false;
    }
  }
  return true;
}

字符流中第一个不重复的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。 当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

如果当前字符流没有存在出现一次的字符,返回#字符。

1、解法一:用Map存储
要求获得第一个只出现一次的。
使用一个有序的存储结构为每个字符计数,再遍历这个对象,第一个出现次数为1的即为结果。在JavaScript中有序存储空间选择Map,用迭代器遍历。
注:不能使用Object,因为Object的key是无序的。

2、解法二:借用C++做法,用数组+ASCII码作为下标
源源不断的有字母放到字符串中,建立一个256个大小的int型数组来代表哈希表,输入的字母作为数组下标,如果字母出现就将哈希表中该下标的值加一。要找第一个只出现一次的字符,就遍历字符串,字符串中的字符作为数组下标,哈希数组值为1对应的字符就是要找的字符。

    let hashTable = new Array(256).fill(0); //建立哈希数组
    let s = ''; 
    function Insert(ch)
    {
        const code = ch.charCodeAt(0);
        if(code > 256) return;
        s = s + ch;   //字符放入字符串中
        hashTable[code]++;    //根据字符,修改哈希数组元素的值
    }
    
    function FirstAppearingOnce()
    {
        for(let i = 0;i<s.length;i++){  //注意的是,要找第一个出现一次的字符,所以遍历字符串,不能遍历哈希数组
            const code = s[i].charCodeAt(0);
            if(hashTable[code] == 1)  //如果字符串作为下标的元素值为1,就说明该字符出现一次,直接返回该字符
                return s[i];
        }
        return '#';
    }

字符串的全排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

思路:回溯法

function swap(arr,i,j) {
    let temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

function Permutation(str) {
  var result = [];
  if (!str) {
    return result;
  }
  var array = str.split('');
  permutate(array, 0, result);
  result.sort();
  return result;
}

function permutate(array, index, result) {
  if (array.length - 1 === index) {
    result.push(array.join(''));
  }
  for (let i = index; i < array.length; i++) {
    swap(array, index, i);
    permutate(array, index + 1, result);
    swap(array, i, index);
  }
}

正则表达式匹配

请实现一个函数用来匹配包括'.'和''的正则表达式。模式中的字符'.'表示任意一个字符,而''表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"abaca"匹配,但是与"aa.a"和"ab*a"均不匹配。

思路:
当模式中的第二个字符不是“*”时:
1、如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。
2、如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。

而当模式中的第二个字符是“*”时:
如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。
如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式:
1、模式后移2字符,相当于x*被忽略;
2、字符串后移1字符,模式后移2字符;
3、字符串后移1字符,模式不变,即继续匹配字符下一位,因为*可以匹配多位;

function match(s, pattern) {
  if (s == undefined || pattern == undefined) {
    return false;
  }
  return matchStr(s, pattern, 0, 0);
}

function matchStr(s, pattern, sIndex, patternIndex) {
  if (sIndex === s.length && patternIndex === pattern.length) {
    return true;
  }
  if (sIndex !== s.length && patternIndex === pattern.length) {
    return false;
  }
  if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] === '*') {
    if (sIndex < s.length && (s[sIndex] === pattern[patternIndex] || pattern[patternIndex] === '.')) {
      return matchStr(s, pattern, sIndex, patternIndex + 2) ||
        matchStr(s, pattern, sIndex + 1, patternIndex + 2) ||
        matchStr(s, pattern, sIndex + 1, patternIndex);
    } else {
      return matchStr(s, pattern, sIndex, patternIndex + 2)
    }
  }
  if (sIndex < s.length && (s[sIndex] === pattern[patternIndex] || pattern[patternIndex] === '.')) {
    return matchStr(s, pattern, sIndex + 1, patternIndex + 1)
  }
  return false;
}

翻转字符串

输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。为简单起见,标点符号和普通字母一样处理。例如输入字符串"I am a student.",则输出"student. a am I"。

解法一:直接调用数组API进行翻转

function ReverseSentence(str)
{
    if(!str){return ''}
    return str.split(' ').reverse().join(' ');
}

解法二:两次翻转单词顺序
第一步将整个字符串翻转,"I am a student." -> ".tneduts a ma I"
第二步将字符串内的单个字符串进行翻转:".tneduts a ma I" -> "student. a am I"

左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如输入字符串"abcdefg"和数字2,该函数将返回左旋转2位得到的结果"cdefgab"。

解法一:拼接两个str

function LeftRotateString(str, n)
{
    if(str&&n!=null){
       return (str+str).substr(n,str.length)
    }else{
        return ''
    }
}

解法二:和上题思路一样,翻转单词顺序

以"abcdefg"为例,将字符串分为两部分ab和cdefg
将两部分分别进行翻转,得到 -> "bagfedc"
再将整个字符串进行翻转,得到 -> "cdefgab"

调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分

思路:
设定两个指针
第一个指针left从数组第一个元素出发,向尾部前进
第二个指针right从数组的最后一个元素出发,向头部前进
left遍历到偶数,right遍历到奇数时,交换两个数的位置
当left>right时,完成交换

function reOrderArray(array) {
    if (Array.isArray(array)) {
        let left = 0;
        let right = array.length - 1;
        while(left < right) {
            while(array[left] % 2 == 1){
                left ++;
            }
            while(array[right] % 2 == 0) {
                right --;
            }
            if(left < right) {
                [array[left],array[right]] = [array[right],array[left]];
            }
        }
    }
    return array;
}

如果要保证奇数和奇数,偶数和偶数之间的相对位置不变。

思路:
首先寻找第一个奇数,并将其放在0号位置。然后将第一个奇数之前的元素全部往后移一位。
依次在第一个奇数之后的元素中寻找奇数,并做移动操作。就可以保证原来的相对顺序。

function reOrderArray2(array) {
    if (Array.isArray(array)) {
        let left = 0;
        let right = 0;
        while(right < array.length) {
            // 找到第一个奇数
            while(array[right] % 2 == 0 && right < array.length) {
                right ++;
            }
            if(right != left && right < array.length) {
                let temp = array[right];
                for (let i = right; i > left; i--) {
                    array[i] = array[i-1];
                }
                array[left] = temp;
            }
            left ++;
            right ++;
        }
    }
    return array;
}

和为S的两个数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

思路:
头尾相加是最快的方法,第一组遇到的和sum相等的值就一定是乘积最小的(和相同,差越大,乘积就越小),大了就大值往前移动,小了就小值往后移动

function reOrderArray(array) {
  if (Array.isArray(array)) {
    let start = 0;
    let end = array.length - 1;
    while (start < end) {
      while (array[start] % 2 === 1) {
        start++;
      }
      while (array[end] % 2 === 0) {
        end--;
      }
      if (start < end) {
        [array[start], array[end]] = [array[end], array[start]]
      }
    }
  }
  return array;
}

和为S的连续正整数序列

输入一个正数S,打印出所有和为S的连续正数序列。

例如:输入15,有序1+2+3+4+5=4+5+6=7+8=15所以打印出3个连续序列1-55-67-8

思路:
创建一个容器child,用于表示当前的子序列,初始元素为1,2
记录子序列的开头元素small和末尾元素big
big向右移动子序列末尾增加一个数small向右移动子序列开头减少一个数
当子序列的和大于目标值,small向右移动,子序列的和小于目标值,big向右移动

function FindContinuousSequence(sum) {
  const result = [];
  const child = [1, 2];
  let big = 2;
  let small = 1;
  let currentSum = 3;
  while (big < sum) {
    while (currentSum < sum && big < sum) {
      child.push(++big);
      currentSum += big;
    }
    while (currentSum > sum && small < big) {
      child.shift();
      currentSum -= small++;
    }
    if (currentSum === sum && child.length > 1) {
      result.push(child.slice());
      child.push(++big);
      currentSum += big;
    }
  }
  return result;
}

鸡蛋炒番茄
1.1k 声望1.3k 粉丝

hello world