算法学习(三): 数组

数组的基本概念


定义:

  • 通常由一组相同类型的元素的集合所组成的结构(javascript允许数组中包含不同类型的元素)
  • 计算机会分配一块连续的内存来存储这个元素集合
  • 利用索引可以取出元素对应的存储地址

特性:

  • 在内存中为连续空间
    定址公式: addr(curElem)=addr(intialElem)+sizeof(curElem)*index
  • 通过index获取数组元素的时间复杂度为O(1)
  • 通常数组第一个位置的索引是0

数组示意图如下:


在java中可以这么定义一个数组

int[] array = {2,4,5,6,22,89,35} //定义数组

array[1] // 值为4 取出索引1位置的值
array[5] // 值为89 取出索引5位置的值

我们还可以定义二维数组

int[][]  array={{1,2,3},{66,45,33,4},{2,7,8}}

三维数组也是可以定义的, 这里就不举例了


为什么使用数组


  • 简化代码, 提高效率
  • 将相同类型的元素组织在一起

比如, 我们要存1-1000这1000个数, 用数组就能很简单的存储, 并且由于数组的每一个元素都是连续开辟的内存空间, 所以也能快速的取到中间任意的值

int[] nums = new int[1000];
for(int i=0; i<nums.length;i++) {
  nums[i] = i+1;
}

nums[5] //6
nums[926] //927


常见数组算法题


因为我自己比较熟悉js, 所以关于算法题的代码, 我都用js代码写, 当然我也只是个初学者, 所以大家不要纠结我代码写的好不好看, 主要看解题思路就好了

例1: 两数之和

给定一个整数数组(无重复元素)和一个目标值, 找出数组中和为目标值的两个数。
按照从小到大的顺序输出结果对
将所有结果对输出到一个数组
例:
Input: number = {2,7,11,15}, target = 9

Output: {2,7}

分析需求:

  1. 数组是否排序
  2. 有没有重复元素
  3. 结果是返回全部还是返回任意一个

解题思路:

思路1:
暴力解法来解

function twoSum(arr, target) {
  let result = []
  let index = 0 
  if(arr.length < 2) {
    return result
  }
  for(let i=0; i<arr.length; i++) {
    for(let j=i+1; j<arr.length; j++) {
      if(arr[i] + arr[j] === target) {
        if(arr[i] < arr[j]) {
          result[index] = []
          result[index].push(arr[i])
          result[index].push(arr[j])
          index++
        }else {
          result[index] = []
          result[index].push(arr[j])
          result[index].push(arr[i])
          index++
        }

      }
    }
  }
  return result
}

let newArray = twoSum([1,3,4,5,6,8,78], 9)
console.log(newArray)

思路1总结:

  • 时间复杂度O(n2)
  • 存在无意义操作
    例如:
    78已经大于9了, 所以每次对它操作都是在浪费时间



思路2:
排序+两根指针

这里由于我们还没有讲到排序, 所以对于排序我们直接用js里面提供的sort方法, 如果大家以后面试的话, 除非是考官强制要求不能使用api, 否则能用编程语言提供的api, 我们一定要使用语言自带的api, 这样能节省很多时间

两根指针的方法在面试中是一个很实用的方法

  • 通过对数组排序与两个指针组合, 减少无意义的遍历
  • 两根指针: 一根指针(start)指向数组的第一个元素(数组中最小元素), 另一个指针(end)指向数组最后一个元素(数组中最大元素)
  • 核心思想: 如果现在两根指针所指元素之和大于目标值, 则表明现在两数之和过大, 应使end指针指向更小的数, 即索引减小(end--), 反之则表明现在两数之和过小, 应使start指针指向更大的数, 即索引增加(start++)
function sum(arr, target) {
  let start = 0
  let end = arr.length-1
  let result = []
  let index = 0
  if(arr.length < 2) {
    return result
  }
  arr.sort((x,y) => {
    return x-y
  })
  while(start < end) {
    if(arr[start] + arr[end] > target) {
      end--
      continue
    }else if(arr[start] + arr[end] < target) {
      start++
      continue
    }else if(arr[start] + arr[end] === target) {
      result[index] = []
      result[index].push(arr[start])
      result[index].push(arr[end])
      start++
      index++
      continue
    }
  }
  return result
}

思路2总结:

  • 通过对数组排序与两根指针组合, 减少无意义的遍历
  • 使用两个指针(而不是一个)以相同/相反的方向遍历数组
  • 时间复杂度分析: 排序: O(nlogn), 两根指针算法: O(n)
    时间复杂度: O(nlogn) + O(n) => O(nlogn)


例2: 三数之和

给定一个包含n个整数的数组(无重复元素)和一个目标值, 找出数组中和为目标值的三个数
例如:
给定一个数组nums = [-1, 0 ,1, 2, -5, 3], target = 0
满足要求的三元组集合为:[[-1,0,1], [2,-5,3]]

思路1:

  • 暴力解法
    算法复杂度: O(n3)
    注意: 面试的时候这种算法基本上不用写了, 太蠢了, 最多可以当成你思路的第一步告诉面试官
  • 尝试用排序+两根指针算法
  • 遍历第一个数字num1, 看看另外两个数之和是否能满足target - num1, 这就转化成两数之和的问题了
  • 时间复杂度: 排序O(nlogn), 两数之和: O(n2)
    O(nlogn) + O(n2) => O(n2)
function sum(arr, target) {
  let result = []
  let first
  let second = arr.length - 1
  let index = 0
  if(arr.length < 3) {
    return result
  }
  arr.sort((x,y) => {
    return x-y
  })
  for(let i=0; i<arr.length; i++ ) {
    first = i+1
    while(first < second) {
      if(arr[first] + arr[second] > target - arr[i]) {
        second--
        continue
      }else if(arr[first] + arr[second] < target - arr[i]){
        first++
        continue
      }else if(arr[first] + arr[second] === target - arr[i]) {
        result[index] = []
        result[index].push(arr[i])
        result[index].push(arr[first])
        result[index].push(arr[second])
        first++
        index++
        continue
      }
    }
  }
  return result
}


K-sum解法总结

  • 排序
  • 尝试遍历第一个数, 将问题转化成(K-1)Sum, 以此类推
  • 时间复杂度
    2-Sum: O(nlogn) + O(nlogn) => O(nlogn)
    3-Sum: O(nlogn) + O(n2) => O(n2)
    4--Sum: O(nlogn) + O(3) => O(n3
    5-Sum: O(nlogn) + O(n(k-1)) => O(n(k-1))


例3: 反转数组

给定一个数组, 反转数组中的所有数字
例:
Input: {1,2,88,45,3,7}
Output: {7,3,45,88,2,1}

分析:

  • 数组是否合法
  • 数组是否已经排序
  • 数组是否有重复元素

这到底就很简单了

  • 用双指针, fisrt指向数组的第一个, end指向数组的最后一个
  • 交换arr[first]和arr[end]的值.然后指针first++往后移一位, 同时end--往前移一位
  • 直到first >= end(指针first和end相遇, 即表示整个数组已经遍历完), 结束交换
function reverse(arr) {
  let first = 0
  let end = arr.length - 1
  
  while(first < end) {
    [arr[first], arr[end]] = [arr[end], arr[first]]
    first++
    end--
  }
  return arr
}

算法复杂度: O(n)


例4: 奇数偶数排序

给定一组数组, 对它们进行排序, 以便所有奇数整数在偶数整数之前出现。元素顺序可以改变。排序的奇数和偶数的顺序无关紧要。
例:
Input:{4,3,5,2,1,11,0,8,6,9}
Output:{9,3,5,11,1,2,0,8,6,4}

思路:

  • 第一步先用暴力解法:
    额外开辟两个数组, 一个数组存储奇数, 一个数组存储偶数, 然后依次先把奇数输出, 再把偶数输出到另外一个新数组, 这种解法时间复杂度为O(n), 但是要另外开辟三个新数组, 所以要浪费额外空间复杂度O(n)
  • 用双指针
    1. 跟之前所有套路一样, first指向数组第一个, end指向数组最后一个
    2. 从数组的第一个开始判断, 如果是奇数, 则first++,指向数组的下一个元素,继续判断, 直到遇到偶数,指针first记录偶数的位置。 这时候从数组的最后一个开始判断, 如果是偶数, 则end--, 指针指向数组前一个元素, 继续判断, 直到遇到奇数, end记录奇数的位置。交换first位置和end位置的两个数
    3. 交换完成后, 按照第二步的方法继续往后判断, 知道firt指针和end指针相遇(first >= end), 就完成了排序
function swap(arr) {
  let first = 0
  let end = arr.length-1
  while(first < end) {
    while(first < end && arr[first] % 2 === 1) {
      first++
    }
    while(first < end && arr[end] % 2 === 0) {
      end--
    }
    [arr[first],arr[end]] = [arr[end], arr[first]]
    first++
    end--
  }
  return arr
}

总结:

  • 因为这个算法只遍历了一遍数组, 所以时间复杂度: O(n)
  • 没有开辟额外的内存空间, 所以空间复杂度: O(1)
  • 相比暴力算法时间复杂度是一样的, 但是节省了空间

例5: 合并两个有序数组

给定两个有序整数数组, 按照递增顺序将他们合并到一个排序数组中
Input: {1,3,5}, {2,4,6}
Output: {1,2,3,4,5,6}

分析:

  • 数组是否已排序
  • 数组是否有重复
  • 返回什么数据

思路:

  • 同样用双指针, 但是这次不太一样, 这次的双指针分别指向两个数组, 并且指针移动方向相同
  • 开辟一个新数组保存排序后的数组, 数组长度为两个待排序数组长度之和
  • 两个数组都从第一个开始遍历, 从第一个位置开始比较两个数组元素的值, 值比较小的那个元素输入到新开辟的数组中的第一个位置, 然后小值所在的数组索引+1, 大值得数组索引不变
  • 拿之前小值所在的数组位置1的元素跟大值数组位置0的做对比, 跟上面上一样, 哪个值小, 他所在的数组索引值就+1, 值大的那个数组索引不变, 继续比直到其中一个数组所有元素都遍历完了
  • 当一个数组所有的值遍历完, 另一个数组还没有遍历完时, 将没有遍历完的数组剩下的值依次输入到新数组中余下的位置
  • 新数组中的元素就是排序结果
function merge(arr1, arr2) {
  let mergeArr = new Array(arr1.length + arr2.length)
  let [index,index1,index2] = [0,0,0]
  
  while(index1 < arr1.length && index2 < arr2.length) {
    if(arr1[index1] <= arr2[index2]) {
      mergeArr[index] = arr1[index1]
      index1++
      index++
    }else if(arr1[index1] > arr2[index2]) {
      mergeArr[index] = arr2[index2]
      index2++
      index++
    }
  }
  
  for(let i = index1; i<arr1.length; i++) {
    mergeArr[index] = arr1[i]
    index++
  }
  
  for(let i = index2; i<arr1.length; i++) {
    mergeArr[index] = arr2[i]
    index++
  }
  return mergeArr
}

总结:

  • 时间复杂度: 因为分别需要遍历两个数组, 所以为O(m+n)
  • 空间复杂度: 开辟了新的数组用来保存排序后的结果, 所以额外空间复杂度为O(m+n),
    这个空间是必须开辟的, 所以这并不算浪费空间

关于数组我们就说到这里, 请大家期待下次的更新吧.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容