序文
リンクリストに関連する質問は、インタビューで頻繁に表示されます。これらの質問は、他の複雑な問題を解決するための基礎となることがよくあります。
この記事では、リンクリストの問題の問題と解決策を整理します。あなたが助けてくれるなら、好きで注意を払ってください。これは私にとって本当に重要です。
目次
1。概要
1.1リンクリストの定義
リンクリストは、一般的な基本データ構造であり、線形リストです。シーケンステーブルとは異なり、リンクリスト内の各ノードは順番に格納されませんが、ノードのポインタフィールドを介して次のノードを指します。
1.2リンクリストの長所と短所
1.3リンクリストの種類
シングルリンクリスト、ダブルリンクリスト、循環リンクリスト、静的リンクリスト
2.リンクリストノードを削除します
リンクリストノードを削除するときは、リンクリストの最初のノード(先行ノードなし)が削除される可能性があることを考慮して、コーディングの便宜のために、センチネルノードの追加を検討してください 。その中で、リンクリストの下からN番目のノードを削除する問題では、スキャンで下からN番目のノードを見つけるために速度ポインタを使用することがより重要なプログラミングスキルです。
237.リンクリスト内のノードを削除する [問題解決策]
203.リンクリスト要素の削除 [問題解決策]
不移除野指针
class Solution {
fun removeElements(head: ListNode?, `val`: Int): ListNode? {
// 哨兵节点
val sentinel = ListNode(-1)
sentinel.next = head
var pre = sentinel
var cur: ListNode? = sentinel
while (null != cur) {
if (`val` == cur.`val`) {
// 移除
pre.next = cur.next
} else {
pre = cur
}
cur = cur.next
}
return sentinel.next
}
}
移除野指针
class Solution {
fun removeElements(head: ListNode?, `val`: Int): ListNode? {
// 哨兵节点
val sentinel = ListNode(-1)
sentinel.next = head
var pre = sentinel
var cur: ListNode? = sentinel
while (null != cur) {
val removeNode = if (`val` == cur.`val`) {
// 移除
pre.next = cur.next
cur
} else {
pre = cur
null
}
cur = cur.next
if (null != removeNode) {
removeNode.next = null
}
}
return sentinel.next
}
}
19.リストの最後からN番目のノードを削除するリストの最後からN番目のノードを削除する [問題の解決策]
リンクリストを指定して、リンクリストの下部からn番目のノードを削除し、リンクリストのヘッドノードを返します。
class Solution {
fun removeNthFromEnd(head: ListNode, n: Int): ListNode? {
// 哨兵节点
val sentinel = ListNode(-1)
sentinel.next = head
var fast: ListNode? = sentinel
var slow: ListNode? = sentinel
for (index in 0 until n) {
fast = fast!!.next
}
// 找到倒数第 k 个节点的前驱
while (null != fast!!.next) {
fast = fast.next
slow = slow!!.next
}
slow!!.next = slow.next!!.next
return sentinel.next
}
}
複雑さの分析:
同様に、876のミドルノード。リンクリストのミドル [問題解決策] も、速度ポインタを介してミドルノードを見つけます。
class Solution {
fun middleNode(head: ListNode?): ListNode? {
if (null == head || null == head.next) {
return head
}
var fast = head
var slow = head
while (null != fast && null != fast.next) {
fast = fast.next!!.next
slow = slow!!.next
}
return slow
}
}
86.パーティションリスト [問題解決策]
指定された値valに等しいリンクリスト内のすべてのノードを削除します。
アイデア:リンクリストを分離することは、val以上のノードを元のリンクリストから2番目のリンクリストに削除し、最後に2つのリンクリストを接続することに他なりません。
class Solution {
fun partition(head: ListNode?, x: Int): ListNode? {
if (null == head) {
return null
}
// 哨兵节点
val sentinel = ListNode(-1)
sentinel.next = head
var pre = sentinel
// 第二链表
var bigHead : ListNode? = null
var bigRear = bigHead
var cur = head
while (null != cur) {
if (cur.`val` >= x) {
// 大于等于:移除
pre.next = cur.next
if(null == bigHead){
bigHead = cur
bigRear = cur
}else{
bigRear!!.next = cur
bigRear = cur
}
} else {
pre = cur
}
if (null == cur.next) {
// 拼接
pre.next = bigHead
bigRear?.next = null
break
}
cur = cur.next
}
return sentinel.next
}
}
複雑さの分析:
328.奇数リンクリスト 【問題解決】
アイデア:奇数-偶数リンクリストは、最初に1つのリンクリストに奇数ノードを配置し、別のリンクリストに偶数ノードを配置し、最後に偶数ノードを奇数リンクリストの最後に接続することに他なりません。
class Solution {
fun oddEvenList(head: ListNode?): ListNode? {
if (null == head) {
return null
}
var odd: ListNode = head
var even = head.next
val evenHead = even
while (null != even && null != even.next) {
// 偶节点
odd.next = even.next
odd = odd.next!!
// 奇节点
even.next = odd.next
even = even.next
}
odd.next = evenHead
// 头节点不动
return head
}
}
83.ソート済みリストから重複を削除するソート済みリスト内の重複要素を削除する
82.ソート済みリストIIから重複を削除するソート済みリストIIから重複を削除する
3.逆リンクリスト
面接での逆リンクリストの質問の頻度は 非常に高いです。何度か面接を受けた学生はこの見方に同意すると思います。ここで、単純なものから難しいものまで、逆リンクリストに関する4つの問題を見つけました。ぜひお試しください。
206.逆リンクリスト 【問題解決策】
単一リンクリストを逆にします。
解決策1:再帰
class Solution {
fun reverseList(head: ListNode?): ListNode? {
if(null == head || null == head.next){
return head
}
val prefix = reverseList(head.next)
head.next.next = head
head.next = null
return prefix
}
}
复制代码
複雑さの分析:
解決策2:反復
class Solution {
fun reverseList(head: ListNode?): ListNode? {
var cur: ListNode? = head
var headP: ListNode? = null
while (null != cur) {
val tmp = cur.next
cur.next = headP
headP = cur
cur = tmp
}
return headP
}
}
複雑さの分析:
92.逆リンクリストII [質問の解決策]
リンクリストが与えられたら、リンクリストを回転させ、リンクリストの各ノードをk位置右に移動します。ここで、kは非負の数です。
class Solution {
fun reverseBetween(head: ListNode?, m: Int, n: Int): ListNode? {
if (null == head || null == head.next) {
return head
}
// 哨兵节点
val sentinel = ListNode(-1)
sentinel.next = head
var rear = sentinel
// 1\. 找到反转开始位置前驱节点
var cur = sentinel
for (index in 0 until m - 1) {
cur = cur.next!!
rear = cur
}
// 2\. 反转指定区域
rear.next = reverseList(rear.next!!, n - m + 1)
return sentinel.next
}
/**
* 反转指定区域
* @param size 长度
*/
fun reverseList(head: ListNode, size: Int): ListNode? {
var cur: ListNode? = head
var headP: ListNode? = null
// 反转的起始点需要连接到第 n 个节点
val headTemp = head
var count = 0
while (null != cur && count < size) {
val tmp = cur.next
cur.next = headP
headP = cur
cur = tmp
count++
}
// 连接到第 n 个节点
headTemp.next = cur
return headP
}
}
複雑さの分析:
234.回文リンクリスト 【問題解決策】
リンクリストが回文リンクリストであるかどうかを確認してください。
アイデア:高速ポインタと低速ポインタを使用して中間ノードを見つけ、リンクリストの後半を反転し(反転リンクリストIIに基づく)、2つのリンクリストが同じかどうかを比較し、最後に反転して元に戻ります。リンクリスト。
class Solution {
fun isPalindrome(head: ListNode?): Boolean {
if (null == head || null == head.next) {
return true
}
// 1\. 找到右边中节点(右中节点)
var fast = head
var slow = head
while (null != fast && null != fast.next) {
slow = slow!!.next
fast = fast.next!!.next
}
// 2\. 反转后半段
val reverseP = reverseList(slow!!)
// 3\. 比较前后两段是否相同
var p = head
var q: ListNode? = reverseP
var isPalindrome = true
while (null != p && null != q) {
if (p.`val` == q.`val`) {
p = p.next
q = q.next
} else {
isPalindrome = false
break
}
}
// 4\. 恢复链表
reverseList(reverseP)
return isPalindrome
}
/**
* 反转链表
*/
private fun reverseList(head: ListNode): ListNode {
// 略,见上一节...
}
}
複雑さの分析:
25.kグループのKリバースノード
リンクリストを提供します。k個のノードごとに反転します。反転したリンクリストに戻ってください。
4.順序付けられたリンクリストを組み合わせる
2つの順序付きリストをマージする、インタビューでのソートされたリンクリストの問題の頻度を より高くマージすること は比較的簡単であり 、より包括的に考慮される要素のK昇順リストの高度なバージョンの マージ、難易度も強化されました。それを試してみてください。
21.2つのソートされたリストをマージする [問題解決策]
2つの昇順リンクリストを新しい昇順リンクリストに結合して戻ります。新しいリンクリストは、指定された2つのリンクリストのすべてのノードを接続することによって構成されます。
class Solution {
fun mergeTwoLists(l1: ListNode?, l2: ListNode?): ListNode? {
if (null == l1) return l2
if (null == l2) return l1
// 哨兵节点
val sentinel = ListNode(-1)
var rear = sentinel
var p = l1
var q = l2
while (null != p && null != q) {
if (p.`val` < q.`val`) {
rear.next = p
rear = p
p = p.next
} else {
rear.next = q
rear = q
q = q.next
}
}
rear.next = if (null != p) p else q
return sentinel.next
}
}
複雑さの分析:
23.k個のソート済みリストをマージする [問題解決策]
リンクリストの配列を提供します。各リストは昇順で配置されています。すべてのリンクリストを昇順のリンクリストにマージして、マージされたリンクリストを返してください。
解決策1:暴力
アイデア1:2つの順序付けられたリンクリストをマージするのと同様に、最小のノードは各ラウンドのk個のリンクリストから取得され、結果のリンクリストに挿入されます。その中で、k個の数から最小のノードを抽出する時間計算量はO(k)O(k)O(k)です。
アイデア2:このアイデアは前のアイデアと似ています。時間計算量とスペース計算量のページは同じです。つまり、k個のリンクリストと結果のリンクリストが順番にマージされます。
略
複雑さの分析:
解決策2:並べ替え方法
アイデア:すべてのノードを配列に格納した後、クイックソートを実行し、配列を単一リンクリストに出力します。
class Solution {
fun mergeKLists(lists: Array<ListNode?>): ListNode? {
if (lists.isNullOrEmpty()) {
return null
}
// 1\. 用一个数组保存所有节点
val array = ArrayList<ListNode>()
for (list in lists) {
var cur = list
while (null != cur) {
array.add(cur)
cur = cur.next
}
}
// 2\. 快速排序
array.sortWith(Comparator { node1, node2 -> node1.`val` - node2.`val` })
// 3\. 输出为链表
val newHead = ListNode(-1)
var rear = newHead
for (node in array) {
rear.next = node
rear = node
}
return newHead.next
}
}
複雑さの分析:
解決策3:マージ方法
アイデア:リンクリストのkグループを2つの部分に分割し、リンクリストの2つのグループを再帰的に処理して、最後にそれらをマージします。
class Solution {
// 合并 k 个有序链表
fun mergeKLists(lists: Array<ListNode?>): ListNode? {
if (lists.isNullOrEmpty()) {
return null
}
return mergeKLists(lists, 0, lists.size - 1)
}
fun mergeKLists(lists: Array<ListNode?>, left: Int, right: Int): ListNode? {
if (left == right) {
return lists[left]
}
// 归并
val mid = (left + right) ushr 1
return mergeTwoLists(
mergeKLists(lists, left, mid),
mergeKLists(lists, mid + 1, right)
)
}
// 合并两个有序链表
fun mergeTwoLists(l1: ListNode?, l2: ListNode?): ListNode? {
// 略,见上一节...
}
}
複雑さの分析:
解決策4:スモールトップパイル法
class Solution {
// 合并 k 个有序链表
fun mergeKLists(lists: Array<ListNode?>): ListNode? {
if (lists.isNullOrEmpty()) {
return null
}
// 最小堆
val queue = PriorityQueue<ListNode>(lists.size) { node1, node2 -> node1.`val` - node2.`val` }
// 1\. 建堆
for (list in lists) {
if (null != list) {
queue.offer(list)
}
}
val sentinel = ListNode(-1)
var rear = sentinel
// 2\. 出队
while (queue.isNotEmpty()) {
val node = queue.poll()!!
// 输出到结果链表
rear.next = node
rear = node
// 存在后继节点,加入堆中
if (null != node.next) {
queue.offer(node.next)
}
}
return sentinel.next
}
}
複雑さの分析:
5.リンクリストを並べ替える
147.リンクリストを挿入してソートするための挿入ソートリスト | 【問題解決策】
148.ソートリスト [問題解決策]
6.循環リンクリスト
リンクリストの交差とリング形成の問題は、インタビューで頻繁に表示される質問のカテゴリに分類できます。前回の記事では、「アルゴリズムの交差の質問|リンクリストの交差とリングの形成の問題」について個別に説明しました。
やっと
国内のインターネットインタビュープロセスは徐々に海外に近づいています。BytedanceやBATのような大企業にとって、アルゴリズムの質問を細断することは必須になっています。これは、企業が人々に面接してスクリーニングするための低コストの方法でもあります。アルゴリズムを作成して合格すると、賢いか勤勉になります。
皆様のご注意とご準備を賜りますようお願い申し上げます。
さらに、今回編集したAndroidの最も重要で人気のある学習の方向性を、GitHubに配置しました。これには、さまざまな方向の自己学習プログラミングルート、質問のコレクション/顔のインタビュー、一連の技術記事も含まれています。
リソースは継続的に更新されており、誰もが一緒に学び、議論することを歓迎します。
元のアドレス:https://juejin.cn/post/6882370280946302983