4주차 1차 1 - LeetCode-Study-Team/solutions GitHub Wiki
용어 정리
연결 리스트(LinkedList)
각 노드가 데이터와 포인터를 가지고 한 줄로 연결되어 있는 방식의 자료구조입니다
장점과 단점
중간에 데이터를 추가나 삭제하더라도 전체의 인덱스가 한 칸씩 뒤로 밀리거나 당겨지는 일이 없기에 ArrayList에 비해서 데이터의 추가나 삭제가 용이하나, 인덱스가 없기에 특정 요소에 접근하기 위해서는 순차 탐색이 필요로 하여 탐색 속도가 떨어진다는 단점이 있습니다.
출처 [코딩팩토리] 자바 LinkedList 사용법 & 예제 총정리
https://coding-factory.tistory.com/552
문제 요약
주어진 head
, linked list 에 사이클이 있는지 확인합니다.
pos
는 파라미터로 전달되지 않습니다.
linked list 에 사이클이 있으면 true
, 아니면 false
를 반환
Constraints:
The number of the nodes in the list is in the range [0, 10^4]
.
-10^5 <= Node.val <= 10^5
pos
는 -1
또는 linked-list 에서 유효한 인덱스
Follow up: Can you solve it using O(1) (i.e. constant) memory?
class ListNode
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
Discuss 01
[HT_Wang]
I don't see why we need 'pos' in the inputs,
while we don't see it in the code. It's kind confusing.
[alexidealex]
When I read the description I was going to write the solution like this:
if (p > 0)
return true;
else
return false;
Discuss 02
[fabrizio3]
/**
1. Use two pointers, walker and runner.
2. walker moves step by step. runner moves two steps at time.
3. if the Linked List has a cycle walker and runner will meet at some point.
*/
public boolean hasCycle(ListNode head) {
if(head==null) return false;
ListNode walker = head;
ListNode runner = head;
while(runner.next!=null && runner.next.next!=null) {
walker = walker.next;
runner = runner.next.next;
if(walker==runner) return true;
}
return false;
}
[jianchao-li]
The if(head==null) check can be further removed.
while(runner!=null && runner.next!=null)
Solution
Approach 1: Hash Table
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> nodesSeen = new HashSet<>();
while (head != null) {
if (nodesSeen.contains(head)) {
return true;
}
nodesSeen.add(head);
head = head.next;
}
return false;
}
}
Time complexity : O(n)
Space complexity : O(n)
HashSet 메소드의 시간복잡도 add : O(1) contains : O(1) next : o(h/n), h는 테이블 용량
출처 [GrepIU] JAVA Collection 시간 복잡도/특징 정리
Approach 2: Floyd's Cycle Finding Algorithm
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
Time complexity : O(n)
Space complexity : O(1).
We only use two nodes (slow and fast) so the space complexity is O(1).
용어 정리
백트래킹(BackTracking)
해를 찾아가는 도중에 해가 될 것 같지 않으면 되돌아간다.
이를 가지치기라고 하고 가지치기를 얼마나 잘하느냐에 따라 효율성을 결정한다.
위상 정렬(Topological sort)
어떤 일을 하는 순서를 찾는 알고리즘이다.
즉, 방향 그래프에 존재하는 각 정점들의 선행 순서를 위배하지 않으면서 모든 정점을 나열하는 것
위상 정렬을 이용한 기본적인 해결 방법
-
진입 차수가 0인 정점(즉, 들어오는 간선의 수가 0)을 선택
- 진입 차수가 0인 정점이 여러 개 존재할 경우 어느 정점을 선택해도 무방하다.
- 초기에 간선의 수가 0인 모든 정점을 큐에 삽입
-
선택된 정점과 여기에 부속된 모든 간선을 삭제
- 선택된 정점을 큐에서 삭제
- 선택된 정점에 부속된 모든 간선에 대해 간선의 수를 감소
-
위의 과정을 반복해서 모든 정점이 선택, 삭제되면 알고리즘 종료
출처
[heejeong Kwon] 알고리즘 위상 정렬(Topological Sort)이란
[ChanBlog] 알고리즘 - 백트래킹(Backtracking)의 정의 및 예시문제
https://chanhuiseok.github.io/posts/algo-23/ https://gmlwjd9405.github.io/2018/08/27/algorithm-topological-sort.html
문제 요약
수강해야 하는 총 과정 : numCourses
강의 번호 : 0
to numCourses - 1
.
배열 prerequisites[i] = [ai, bi]
: ai
과정을 수강하려면 bi
과정을 먼저 수강해야 함을 나타냄
모든 과정을 마칠 수 있으면 true
, 없으면 false
.
Constraints:
1 <= numCourses <= 105
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
모든 쌍의 prerequisites[i]
는 고유하다.
백트래킹
유망하지 않은 것 : 루프(싸이클)이 있는 것
문제 풀이
Approach 1: Backtracking
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
// 코스, 다음 코스 목록
HashMap<Integer, List<Integer>> courseDict = new HashMap<>();
// 그래프 빌드
for (int[] relation : prerequisites) {
// relation[0]이 relation[1]에 종속 : relation[0] <- relation[1]
if (courseDict.containsKey(relation[1])) { //ex. [0,1]
courseDict.get(relation[1]).add(relation[0]);
} else {
List<Integer> nextCourses = new LinkedList<>();
nextCourses.add(relation[0]);
courseDict.put(relation[1], nextCourses);
}
}
boolean[] path = new boolean[numCourses];
for (int currCourse = 0; currCourse < numCourses; ++currCourse) {
if (this.isCyclic(currCourse, courseDict, path)) {
return false;
}
}
return true;
}
/*
* 백트래킹 메소드 : currCourse에서 시작하여 사이클이 형성되지 않는지 체크하는 메소드
*/
protected boolean isCyclic(
Integer currCourse,
HashMap<Integer, List<Integer>> courseDict,
boolean[] path) {
if (path[currCourse]) {
// 이전 방문한 노드를 발견한다. 즉, 사이클을 감지한다
return true;
}
// 다음 과정이 없음, 루프가 없음
if (!courseDict.containsKey(currCourse))
return false;
// 백트래킹 하기 전, path에 노드를 표시
path[currCourse] = true;
// 백트래킹
boolean ret = false;
for (Integer nextCourse : courseDict.get(currCourse)) {
ret = this.isCyclic(nextCourse, courseDict, path);
if (ret)
break;
}
// 백트래킹 한 이후, path에서 노드 제거
path[currCourse] = false;
return ret;
}
}
E는 종속성의 수, V는 코스의 수
시간 복잡도 : O(E+V^2)
공간 복잡도 : O(E+V)
Approach 2: Postorder DFS (Depth-First Search)
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
// course -> list of next courses
HashMap<Integer, List<Integer>> courseDict = new HashMap<>();
// build the graph first
for (int[] relation : prerequisites) {
// relation[0] depends on relation[1]
if (courseDict.containsKey(relation[1])) {
courseDict.get(relation[1]).add(relation[0]);
} else {
List<Integer> nextCourses = new LinkedList<>();
nextCourses.add(relation[0]);
courseDict.put(relation[1], nextCourses);
}
}
boolean[] checked = new boolean[numCourses];
boolean[] path = new boolean[numCourses];
for (int currCourse = 0; currCourse < numCourses; ++currCourse) {
if (this.isCyclic(currCourse, courseDict, checked, path))
return false;
}
return true;
}
/*
* postorder DFS check that no cycle would be formed starting from currCourse
*/
protected boolean isCyclic(
Integer currCourse, HashMap<Integer, List<Integer>> courseDict,
boolean[] checked, boolean[] path) {
// bottom cases
if (checked[currCourse])
// this node has been checked, no cycle would be formed with this node.
return false;
if (path[currCourse])
// come across a previously visited node, i.e. detect the cycle
return true;
// no following courses, no loop.
if (!courseDict.containsKey(currCourse))
return false;
// before backtracking, mark the node in the path
path[currCourse] = true;
boolean ret = false;
// postorder DFS, to visit all its children first.
for (Integer child : courseDict.get(currCourse)) {
ret = this.isCyclic(child, courseDict, checked, path);
if (ret)
break;
}
// after the visits of children, we come back to process the node itself
// remove the node from the path
path[currCourse] = false;
// Now that we've visited the nodes in the downstream,
// we complete the check of this node.
checked[currCourse] = true;
return ret;
}
}
Approach 3: Topological Sort
class GNode {
public Integer inDegrees = 0;
public List<Integer> outNodes = new LinkedList<Integer>();
}
class Solution {
public boolean canFinish(int numCourses, int[][] prerequisites) {
if (prerequisites.length == 0)
return true; // no cycle could be formed in empty graph.
// course -> list of next courses
HashMap<Integer, GNode> graph = new HashMap<>();
// build the graph first
for (int[] relation : prerequisites) {
// relation[1] -> relation[0]
GNode prevCourse = this.getCreateGNode(graph, relation[1]);
GNode nextCourse = this.getCreateGNode(graph, relation[0]);
prevCourse.outNodes.add(relation[0]);
nextCourse.inDegrees += 1;
}
// We start from courses that have no prerequisites.
int totalDeps = prerequisites.length;
LinkedList<Integer> nodepCourses = new LinkedList<Integer>();
for (Map.Entry<Integer, GNode> entry : graph.entrySet()) {
GNode node = entry.getValue();
if (node.inDegrees == 0)
nodepCourses.add(entry.getKey());
}
int removedEdges = 0;
while (nodepCourses.size() > 0) {
Integer course = nodepCourses.pop();
for (Integer nextCourse : graph.get(course).outNodes) {
GNode childNode = graph.get(nextCourse);
childNode.inDegrees -= 1;
removedEdges += 1;
if (childNode.inDegrees == 0)
nodepCourses.add(nextCourse);
}
}
if (removedEdges != totalDeps)
// if there are still some edges left, then there exist some cycles
// Due to the dead-lock (dependencies), we cannot remove the cyclic edges
return false;
else
return true;
}
/**
* Retrieve the existing <key, value> from graph, otherwise create a new one.
*/
protected GNode getCreateGNode(HashMap<Integer, GNode> graph, Integer course) {
GNode node = null;
if (graph.containsKey(course)) {
node = graph.get(course);
} else {
node = new GNode();
graph.put(course, node);
}
return node;
}
}